🔎 How to download & run the codes?

All the source codes of the aggregation methods are available here . To run the codes, you can clone the repository directly or simply load the R script source file from the repository using devtools package in Rstudio as follow:

  1. Install devtools package using command:

    install.packages("devtools")

  2. Loading the source codes from GitHub repository using source_url function by:

    devtools::source_url("https://raw.githubusercontent.com/hassothea/AggregationMethods/main/KernelAggReg.R")


✎ Note: All codes contained in this Rmarkdown are built with recent version of (version \(>\) 4.1, available here) and Rstudio (version > 2022.02.2+485, available here). Note also that the code chucks are hidden by default.

To see the codes, you can:


1 KFC procedure & important packages

1.1 KFC procedure

KFC procedure is a three-step methodology which puts together clustering and consensual aggregation methods to construct predictions in supervised learning problems. The three steps of the procedure are:

  • Step K : \(K\)-means clustering algorithm is implemented on the input data using several options of Bregman divergences \(({\cal B}_j)_{j=1}^M\) (\(M\) is the number of total divergences used), therefore, the input data is partitioned into many different structures, according to the Bregman divergence used.
  • Step F : For a partition structure given by a divergence \({\cal B}_j\), we fit simple models (linear, for example) on all the clusters of the obtained partition. Then, the collection \({\cal M}_j=\{{\cal M}_{j,k}\}_{k=1}^K\) of these local models is called candidate model, corresponding to the Bregman divergence \({\cal B}_j\). At the end of this step, several candidate models are constructed.
  • Step C : This step aggregates the obtained candidate models using consensual aggregation methods studied in Has (2021) or Fischer and Mougeot (2019).

Remark.1: The prediction of any observation \(x\) given by a candidate model \({\cal M}_j\) is done in two simple steps:

  1. \(x\) is classified into one of the obtained clusters using the corresponding divergence \({\cal B}_j\), i.e., \[x\in{\cal C}_{k^*} \Leftrightarrow {\cal B}_j(c_{k^*},x)=\inf_{1\leq k\leq K}{\cal B}_j(c_k,x)\] where \(\{c_1,...,c_K\}_{k=1}^K\) are the centroids of the corresponding clusters \(\{C_1,...,C_K\}\).

  2. The prediction of \(x\) is given by the corresponding local model \({\cal M}_{j,k^*}\) defined on cluster \(k^*\), i.e., \({\cal M}_j(x)={\cal M}_{j,k^*}(x)\).


The figure above represents the summary of KFC procedure

1.2 Important packages

We prepare all the necessary tools for this Rmarkdown. pacman package allows us to load (if exists) or install (if does not exist) any available packages from The Comprehensive R Archive Network (CRAN) of .

# Check if package "pacman" is already installed 

lookup_packages <- installed.packages()[,1]
if(!("pacman" %in% lookup_packages))
  install.packages("pacman")


# To be installed or loaded
pacman::p_load(magrittr)
pacman::p_load(tidyverse)

## package for "generateMachines"
pacman::p_load(tree)
pacman::p_load(glmnet)
pacman::p_load(randomForest)
pacman::p_load(FNN)
pacman::p_load(xgboost)
pacman::p_load(keras)
pacman::p_load(pracma)
pacman::p_load(latex2exp)
pacman::p_load(plotly)
pacman::p_load(parallel)
pacman::p_load(foreach)
pacman::p_load(doParallel)
rm(lookup_packages)

2 Bregman divergences (BD)

Definition Let \(\phi:\mathcal{C}\rightarrow\mathbb{R}\) be a strictly convex and continuously differentiable function defined on a measurable convex subset \(\mathcal{C}\subset\mathbb{R}^d\). Let \(int(\mathcal{C})\) denote its relative interior. A Bregman divergence indexed by \(\phi\) is a dissimilarity measure \(d_{\phi}:\mathcal{C}\times int(\mathcal{C})\rightarrow\mathbb{R}\) defined for any pair \((x,y)\in \mathcal{C}\times int(\mathcal{C})\) by, \[\begin{equation} \label{eq:1.10} d_{\phi}(x,y)=\phi(x)-\phi(y)-\langle x-y,\nabla\phi(y)\rangle \end{equation}\] where \(\nabla\phi(y)\) denotes the gradient of \(\phi\) computed at a point \(y\in int(\mathcal{C})\). A Bregman divergence is not necessarily a metric as it may not be symmetric and the triangular inequality might not be satisfied.

This section defines all the Bregman divergences used. The list of all the Bregman divergences is given in the table below:


Name \(\phi\) \(d_{\phi}\) \(\cal C\)
Euclidean \({\|x\|_2^2}=\sum_{i=1}^dx_i^2\) \(\|x-y\|_2^2\) \(\mathbb{R}^d\)
General Kullback-Leibler \(\sum_{i=1}^d x_i\ln( x_i)\) \(\sum_{i=1}^d( x_i\ln(\frac{ x_i}{y_i})-(x_i-y_i))\) \((0,+\infty)^d\)
Logistic \(\sum_{i=1}^d(x_i\ln( x_i)+(1- x_i)\ln(1- x_i))\) \(\sum_{i=1}^d\Big( x_i\ln(\frac{x_i}{y_i})+(1- x_i)\ln(\frac{1- x_i}{1-y_i})\Big)\) \((0,1)^d\)
Itakura-Saito \(-\sum_{i=1}^d\ln( x_i)\) \(\sum_{i=1}^d\Big(\frac{ x_i}{y_i}-\ln(\frac{ x_i}{y_i})-1\Big)\) \((0,+\infty)^d\)
Exponential \(\sum_{i=1}^de^{x_i}\) \(\sum_{i=1}^d(e^{x_i}-e^{y_i}-e^{y_i}(x_i-y_i))\) \(\mathbb{R}^d\)
Polynomial \(\sum_{i=1}^d|x|^p,p>2\) \(\sum_{k=1}^d(|x_k|^p-|y_k|^p-\text{sign}(y_k)^pp(x_k-y_k)y_k^{p-1})\) \(\mathbb{R}^d\)

2.1 Look-up list of Bregman divergences

The codes below provide a look-up list of all the BDs defined in the table above.

euclidDiv <- function(X., y., deg = NULL){
    res <- sweep(X., 2, y.)
    return(rowSums(res^2))
}
gklDiv <- function(X., y., deg = NULL){
  res <- c("/", "-") %>%
    map(.f = ~ sweep(X., 2, y., FUN = .x))
  return(rowSums(X.*log(res[[1]]) - res[[2]]))
}
logDiv <- function(X., y., deg = NULL){
    res <-  map2(.x = list(X., 1-X.),
                 .y = list(y., 1-y.),
                 .f = ~ sweep(.x, 2, .y, FUN = "/"))
    return(rowSums(X.*log(res[[1]])+(1-X.)*log(res[[2]])))
}
itaDiv <- function(X., y., deg = NULL){
    res <- sweep(X., 2, y., FUN = "/")
    return(rowSums(res-log(res) - 1))
}
expDiv <- function(X., y., deg = NULL){
    exp_y <- exp(y.)
    res <- sweep(1+X., 2, y.) %>%
      sweep(2, exp_y, FUN = "*")
    return(rowSums(exp(X.)-res))
}
polyDiv <- function(X., y., deg = 3){
    S <- map2(.x = list(X., X.^deg),
              .y = list(y., y.^deg),
              .f = ~ sweep(.x, 
                           MARGIN = 2, 
                           STATS = .y,
                           FUN = "-"))
    if(deg %% 2 == 0){
      Tem <- sweep(S[[1]], 2, y.^(deg-1), FUN = "*")
      res <- rowSums(S[[2]] - deg * Tem)
    }
    else{
      Tem <- sweep(S[[1]], 2, sign(y.) * y.^(deg-1), FUN = "*")
      res <- rowSums(S[[2]] - deg * Tem)
    }
    return(res)
}
lookup_div <- list(
  euclidean = euclidDiv,
  gkl = gklDiv,
  logistic = logDiv,
  itakura = itaDiv,
  exponential = expDiv,
  polynomial = polyDiv
)

2.2 Function : BregmanDiv

This function computes the matrix of BDs between two sets of data points. Each set of data points should be stored as matrices, data frame, or tibble object where each row represents an individual data point.

  • Argument:

    • X., C. : The data matrices, tibbles and data frames, containing the data points for which the Bregman divergences between them are to be computed.
    • div : the type of divergence to be used. It should be a subset of {"euclidean", "gkl", "logistic", "itakura", "exponential", "polynomial"}.
    • deg : the degree of polynomial BD (if one is used).
  • Value:

    This function returns a tibble object \(D=(d_{i,j})\) where \(d_{i,j}\) is the Bregman divergence between row \(i\) of X. and row \(j\) of C..

BregmanDiv <- function(X., 
                       C., 
                       div = c("euclidean",
                                "gkl",
                                "logistic",
                                "itakura",
                                "exponential",
                                "polynomial"),
                       deg = 3){
  div <- match.arg(div)
  d_c <- dim(C.)
  if(is.null(d_c)){
    C <- matrix(C., nrow = 1, byrow = TRUE)
  } else{
    C <- as.matrix(C.)
  }
  if(is.null(dim(X.))){
    X <- matrix(X., nrow = 1, byrow = TRUE)
  } else{
    X <- as.matrix(X.)
  }
  dis <-  map_dfc(.x = 1:dim(C)[1],
                  .f = ~ tibble('{{.x}}' := lookup_div[[div]](X, C[.x,], deg = deg)))
  return(dis)
}

3 Step \(K\): \(K\)-means with Bregman divergences

This section implements \(K\)-means algorithm using Bregman divergences which corresponds to the step \(K\) of KFC procedure.

3.1 Function : findClosestCentroid and newCentroids

These two functions perform the main steps of \(K\)-means algorithm. findClosestCentroid function assigns any data points to some cluster according to the nearest centroid among all the centroids, and newCentroids function compute the new centroids of the partition given the cluster labels of all data points.

  • Argument:

    • x. : The data matrices, tibbles and data frames, containing the data points to be assigned to some cluster.
    • centroids : The matrix or data frame containing the centroids (by row).
    • div : the type of divergence to be used.
    • deg : the degree of polynomial BD (if one is used).
  • Value:

    The each of the two functions returns arguments for one another:

    • findClosestCentroid returns a vector of size equals to the number of rows of data matrix x., containing the cluster labels of the data points.
    • newCentroids returns the matrix of centroids.
findClosestCentroid <- function(x., centroids., div, deg = 3){
  dist <- BregmanDiv(x., centroids., div, deg)
  clust <- 1:nrow(x.) %>%
    map_int(.f = ~ which.min(dist[.x,]))
  return(clust)
}
newCentroids <- function(x., clusters.){
  centroids <- unique(clusters.) %>%
    map_dfr(.f = ~ colMeans(x.[clusters. == .x, ]))
  return(centroids)
}

3.2 Function : kmeansBD

This function performs \(K\)-means algorithm with BDs.

  • Argument:

    • train_input : The data matrices, tibbles and data frames, containing the data points.
    • K : The number of clusters.
    • n_start : the number of times to perform the algorithm, and the best one among them is chosen to be the final result. This is done to avoid local optimal solutions. By default, n_start = 5.
    • maxIter : the maximum number of iterations in case the algorithm does not converge. By default, maxIter = 500.
    • deg : the degree of polynomial BD (if one is used).
    • scale_input : logical value controlling whether to scale the input to be in \((0,1)\) or not. By default, scale_input = FALSE.
    • div : the type of divergence to be used. By default, div = "euclidean" and the usual \(K\)-means algorithm is performed.
    • splits : real number between \(0\) and \(1\) specifying the proportion of training data to be used to perform \(K\)-means algorithm. The remaining part with be used for the aggregation. By default, splits = 1 and all the input data are used.
    • epsilon : the stopping threshold criterion to stop the algorithm. By default, epsilon = 1e-10.
    • center_, scale_ : the center and scale to be used to scale the input data. By default, they are NULL.
    • id_shuffle : a logical vector specifying which part of the training data will be selected to perform the algorithm. This is important when we want to perform the algorithm on the same set of data points but with different BDs.
  • Value:

    This function returns a list of the following objects:

    • centroids : the matrix of the obtained centroids.
    • clusters : a vector of cluster label of the data points.
    • train_data : list of the following important objects
      • X_train : the training data used for the algorithm.
      • X_remain : the remaining part of the input data used for the aggregation.
      • id_remain : the logical vector specifying the remaining part (X_remain) of the input data.
    • parameters : the list of the following objects:
      • div : divergence used.
      • deg : the degree of polynomial BD (if one is used).
      • center_, scale_ : the center and scale used to scale the input data.
    • running_time: the computational time of the algorithm.
kmeansBD <- function(train_input,
                     K,
                     n_start = 5,
                     maxIter = 500,
                     deg = 3,
                     scale_input = FALSE,
                     div = "euclidean",
                     splits = 1,
                     epsilon = 1e-10,
                     center_ = NULL,
                     scale_ = NULL,
                     id_shuffle = NULL){
  start_time <- Sys.time()
  # Distortion function
  X <- as.matrix(train_input)
  N <- dim(X)
  if(scale_input){
    if(!(is.null(center_) & is.null(scale_))){
      if(length(center_) == 1){
        center_ <- rep(center_, N[2])
      }
      if(length(scale_) == 1){
        scale_ <- rep(scale_, N[2])
      }
    } else{
      min_ <- apply(X, 2, FUN = min)
      c_ <- abs(colMeans(X)/5)
      center_ <- min_ - c_
      scale_ <- apply(X, 2, FUN = max) - center_ + 1
    }
    X <- scale(X, center = center_, scale = scale_)
  }
  if(is.null(id_shuffle)){
    train_id <- rep(TRUE, N[1])
    if(splits < 1){
      train_id[sample(N[1], floor(N[1]*(1-splits)))] <- FALSE
    }
  } else{
    train_id <- id_shuffle
  }
  X_train1 <- X[train_id,]
  X_train2 <- X[!train_id,]
  mu <- as.matrix(colMeans(X_train1))
  distortion <- function(clus){
    cent <- newCentroids(X_train1, clus)
    var_within <- 1:K %>%
      map(.f = ~ BregmanDiv(X_train1[clus == .x,], 
                            cent[.x,], 
                            div, 
                            deg)) %>%
      map(.f = sum) %>%
      Reduce("+", .)
    return(var_within)
  }
  # Kmeans algorithm
  kmeansWithBD <- function(x., k., maxiter., eps.) {
    n. <- nrow(x.)
    # initialization
    init <- sample(n., k.)
    centroids_old <- x.[init,]
    i <- 0
    while(i < maxIter){
      # Assignment step
      clusters <- findClosestCentroid(x., centroids_old, div, deg)
      # Recompute centroids
      centroids_new <- newCentroids(x., clusters)
      if ((sum(is.na(centroids_new)) > 0) |
          (nrow(centroids_new) != k.)) {
        init <- sample(n., k.)
        centroids_old <- x.[init,]
        warning("NA produced -> reinitialize centroids...!")
      }
      else{
        if(sum(abs(centroids_new - centroids_old)) > eps.){
          centroids_old <- centroids_new
        } else{
          break
        }
      }
      i <- i + 1
    }
    return(clusters)
  }
  results <- 1:n_start %>% 
    map_dfc(.f = ~ tibble("{{.x}}" := kmeansWithBD(X_train1, 
                                                   K,
                                                   maxIter, 
                                                   epsilon)))
  opt_id <- 1:n_start %>%
    map_dbl(.f = ~ distortion(results[[.x]])) %>%
    which.min
  cluster <- clusters <- results[[opt_id]]
  j <- 1
  ID <- unique(cluster)
  for (i in ID) {
    clusters[cluster == i] = j
    j =  j + 1
  }
  centroids = newCentroids(X_train1, clusters)
  time_taken <- Sys.time() - start_time
  return(
    list(
      centroids = centroids,
      clusters = clusters,
      train_data = list(X_train = X_train1,
                        X_remain = X_train2,
                        id_remain = !train_id),
      parameters = list(div = div,
                        deg = deg,
                        center_ = center_,
                        scale_ = scale_),
      running_time = time_taken
    )
  )
}

Example.1: We perform \(K\)-means algorithm with "gkl" BD on Abalone dataset.


pacman::p_load(readr)
colname <- c("Type", "LongestShell", "Diameter", "Height", "WholeWeight", "ShuckedWeight", "VisceraWeight", "ShellWeight", "Rings")
df <- readr::read_delim("https://archive.ics.uci.edu/ml/machine-learning-databases/abalone/abalone.data", col_names = colname, delim = ",", show_col_types = FALSE)
n <- nrow(df)
train <- logical(n)
train[sample(n,  floor(n*0.8))] <- TRUE
cl <- df[train,2:(ncol(df)-1)] %>%
  kmeansBD(K = 3, div = "gkl", splits = 0.5, scale_input = TRUE)
table(cl$clusters)

  1   2   3 
435 697 539 

4 Step \(F\): Fitting predictive models

This section builds global models by fitting local model on each given cluster of the obtained partition. This corresponds to the step \(F\) of the procedure.

4.1 Function : fitLocalModels

This function fits local models on all clusters of the obtained partition.

  • Argument:

    • kmeans_BD : An object obtained from kmeansBD function.
    • train_response : The vector of response variable corresponding to the full input_data given to kmeanBD function.
    • model : Type of local model to fit on all the clusters of the given partition. It should be either a model object which is compactible
    • formula : The degree of polynomial BD (if one is used).
  • Value:

    This function returns a list of the following objects:

    • local_models : all the local models fitted on all clusters of the given partition.
    • kmeans_BD : the kmeansBD object.
    • data_remain : a list of the following object:
      • fit : the fitted values of the remaining part of the input data.
      • response : the actual response values corresponding to the remaining input data.
    • running_time : the computational time of the algorithm.
fitLocalModels <- function(kmeans_BD,
                           train_response,
                           model = "lm",
                           formula = NULL){
  start_time <- Sys.time()
  X_train <- kmeans_BD$train_data$X_train
  y_train <- train_response[!(kmeans_BD$train_data$id_remain)]
  X_remain <- kmeans_BD$train_data$X_remain
  y_remain <- NULL
  if(!is.null(X_remain)){
    y_remain <- train_response[kmeans_BD$train_data$id_remain]
  }
  pacman::p_load(tree)
  pacman::p_load(randomForest)
  model_ <- ifelse(model == "tree", tree::tree, model)
  K <- nrow(kmeans_BD$centroids)
  if (is.null(formula)){
    form <- formula(target ~ .)
  }
  else{
    form <- update(formula, target ~ .)
  }
  data_ <- bind_cols(X_train, "target":= y_train)
  fit_lookup <- list(lm = "fitted.values",
                     rf = "predicted")
  if(is.character(model_)){
    model_lookup <- list(lm = lm,
                         rf = randomForest::randomForest)
    mod <- map(.x = 1:K, 
               .f = ~ model_lookup[[model_]](formula = form, 
                                             data = data_[kmeans_BD$clusters == .x, ]))
  } else{
    mod <- map(.x = 1:K, 
               .f = ~ model_(formula = form, 
                             data = data_[kmeans_BD$clusters == .x,]))
  }
  pred0 <- NULL
  if(!is.null(X_remain)){
    pred0 <- vector(mode = "numeric", 
                    length = length(y_remain))
    clus <- findClosestCentroid(x. = X_remain,
                                centroids. = kmeans_BD$centroids,
                                div = kmeans_BD$parameters$div,
                                deg = kmeans_BD$parameters$deg)
    for(i_ in 1:K){
      pred0[clus == i_] <- predict(mod[[i_]],
                                   as.data.frame(X_remain[clus == i_,]))
    }
  }
  time_taken <- Sys.time() - start_time
  return(list(
    local_models = mod,
    kmeans_BD = kmeans_BD,
    data_remain = list(fit = pred0,
                       response = y_remain),
    running_time = time_taken
  ))
}

Example.2: From Example.1 above, multiple linear regression models are built on all the obtained clusters. The mean square error of this model, evaluated on the remaining \(50\%\) of the training data is computed.


fit <- fitLocalModels(train_response = df$Rings[train],
                      kmeans_BD = cl,
                      model = "lm")

mean((fit$data_remain$response- fit$data_remain$fit)^2)
[1] 4.71925

4.2 Function : localPredict

This function allows us to predict any new observation using the candidate model \(\cal M_j=({\cal M}_{j,k})_{k=1}^M\) corresponding to Bregman divergence \({\cal B}_j\), for some \(j\in J\subset\{1,...,M\}\).

  • Argument:

    • localModels : The local model object obtained from fitLocalModels function.
    • newData : New input data to be predicted using the candidate models given in localModels argument.
  • Value:

    This function returns a predicted vector of the newData.

localPredict <- function(localModels,
                         newData){
  kmean_BD <- localModels$kmeans_BD
  K <- nrow(kmean_BD$centroids)
  newData_ <- newData
  if(!(is.null(kmean_BD$parameters$center_))){
    newData_ <- scale(newData,
                      center = kmean_BD$parameters$center_,
                      scale = kmean_BD$parameters$scale_)
    id0 <- newData_ <= 0
    if(any(id0)){
      min_ <- min(newData_[id0])
      newData_[id0] <- runif(sum(id0), min(1e-3, min_/10), min_)
    }
  }
  clus <- findClosestCentroid(x. = newData_,
                              centroids. = kmean_BD$centroids,
                              div = kmean_BD$parameters$div,
                              deg = kmean_BD$parameters$deg)
  pred0 <- vector(mode = "numeric", length = nrow(newData_))
  for(i_ in 1:K){
    pred0[clus == i_] <- predict(localModels$local_models[[i_]],
                                 as.data.frame(newData_[clus == i_,]))
  }
  pred0 <- as_tibble(pred0)
  names(pred0) <- ifelse(kmean_BD$parameters$div == "polynomial",
                         paste0("polynomial", kmean_BD$parameters$deg),
                         kmean_BD$parameters$div)
  return(pred0)
}

Example.3: The the performance of the candidate model corresponding to "gkl" divergence is compared to random forest regression on a \(20\%\) testing data.


y_hat <- localPredict(fit,
                      df[!train, 2:(ncol(df)-1),])
rf <- randomForest(Rings ~ ., data = df[train,2:ncol(df)])
mean((predict(rf, newdata = df[!train,2:ncol(df)])- df$Rings[!train])^2)
[1] 4.290698
mean((y_hat$gkl-df$Rings[!train])^2)
[1] 4.072496

5 Step \(C\): Combining methods

The aggregation methods are available here . The aggregation methods are imported into Rstudio environment.

pacman::p_load(devtools)
### Kernel based consensual aggregation
source_url("https://raw.githubusercontent.com/hassothea/AggregationMethods/main/KernelAggReg.R")
ℹ SHA-1 hash of file is a8312879ba2ed0c5335566dd75fb90751025953c
### MixCobra
source_url("https://raw.githubusercontent.com/hassothea/AggregationMethods/main/MixCobraReg.R")
ℹ SHA-1 hash of file is 63f73453cd5d3e7006bd02ee84e8115300a20178

5.1 Function : stepK, stepF and stepC

These functions allow to set the values of the parameters in the three steps of the KFC procedure. Each function returns a list of all the parameters given in its arguments.

stepK = function(K,
                 n_start = 5,
                 maxIter = 300,
                 deg = 3,
                 scale_input = FALSE,
                 div = NULL,
                 splits = 0.75,
                 epsilon = 1e-10,
                 center_ = NULL,
                 scale_ = NULL){
  return(list(K = K,
              n_start = n_start,
              maxIter = maxIter,
              deg = deg,
              scale_input = scale_input,
              div = div,
              splits = splits,
              epsilon = epsilon,
              center_ = center_ ,
              scale_ = scale_))
}

stepF = function(formula = NULL, 
                 model = "lm"){
  return(list(formula = formula, 
              model = model))
}

stepC = function(n_cv = 5,
                 method = c("cobra", "mixcobra"),
                 opt_methods = c("grad", "grid"),
                 kernels = "gaussian",
                 scale_features = FALSE){
  return(list(n_cv = n_cv,
              method = method,
              opt_methods = opt_methods,
              kernels = kernels,
              scale_features = scale_features))
}

6 Function: KFCreg

This function is the complete implementation of KFC procedure.


Remark.2: The parallel argument above requires internet connection to load the source codes of \(K\)-means algorithm with BDs from GitHub . It is performed on the maximum number of clusters of your machine, and the speed is at least two times faster than without parallelism, however, it is not so stable depending on your internet connection or machine. About the aggregation methods of step \(C\),


KFCreg = function(train_input,
                  train_response,
                  test_input,
                  test_response = NULL,
                  n_cv = 5,
                  parallel = TRUE,
                  inv_sigma = sqrt(.5),
                  alp = 2,
                  K_step = stepK(splits = .5),
                  F_step = stepF(),
                  C_step = stepC(),
                  setGradParamAgg = setGradParameter(),
                  setGridParamAgg = setGridParameter(),
                  setGradParamMix = setGradParameter_Mix(),
                  setGridParamMix = setGridParameter_Mix()){
  start_time <- Sys.time()
  lookup_div_names <- c("euclidean",
                         "gkl",
                         "logistic",
                         "itakura",
                         "polynomial")
  div_ <- K_step$div
  ### K step: Kmeans clustering with BDs
  if (is.null(K_step$div)){
    divergences <- lookup_div_names
    warning("No divergence provided! All of them are used!")
  }
  else{
    divergences <- K_step$div %>% 
      map_chr(.f = ~ match.arg(arg = .x, 
                               choices = lookup_div_names))
  }
  div_list <- divergences %>% 
    map(.f = (\(x) if(x != "polynomial") return(x) else return(rep("polynomial", length(K_step$deg))))) %>%
    unlist
  deg_list <- rep(NA, length(div_))
  deg_list[div_list == "polynomial"] <- K_step$deg
  div_names <- map2_chr(.x = div_list,
                        .y = deg_list,
                        .f = (\(x, y) if(is.na(y)) return(x) else return(paste0(x,y))))
  ### Step K: Kmeans clustering with Bregman divergences
  dm <- dim(train_input)
  id_shuffle <- vector(length = dm[1])
  n_train <- floor(K_step$splits * dm[1])
  id_shuffle[sample(dm[1], n_train)] <- TRUE
  if(parallel){
    numCores <- parallel::detectCores()
    doParallel::registerDoParallel(numCores) # use multicore, set to the number of our cores
    kmean_ <- foreach(i=1:length(div_names)) %dopar% {
      devtools::source_url("https://raw.githubusercontent.com/hassothea/KFC-Procedure/master/kmeanBD.R")
      kmeansBD(train_input = train_input,
               K = K_step$K,
               div = div_list[i],
               n_start = K_step$n_start,
               maxIter = K_step$maxIter,
               deg = deg_list[i],
               scale_input = K_step$scale_input,
               splits = K_step$splits,
               epsilon = K_step$epsilon,
               center_ = K_step$center_,
               scale_ = K_step$scale_,
               id_shuffle = id_shuffle)
    }
    doParallel::stopImplicitCluster()
  } else{
    kmean_ <- map2(.x = div_list,
                   .y = deg_list,
                   .f = ~ kmeansBD(train_input = train_input,
                                   K = K_step$K,
                                   div = .x,
                                   n_start = K_step$n_start,
                                   maxIter = K_step$maxIter,
                                   deg = .y,
                                   scale_input = K_step$scale_input,
                                   splits = K_step$splits,
                                   epsilon = K_step$epsilon,
                                   center_ = K_step$center_,
                                   scale_ = K_step$scale_,
                                   id_shuffle = id_shuffle))
  }
  names(kmean_) <- div_names
  ### F step: Fitting the corresponding model on each observed cluster
  model_ <- div_names %>%
    map(.f = ~ fitLocalModels(kmean_[[.x]],
                              train_response = train_response,
                              model = F_step$model,
                              formula = F_step$formula))
  names(model_) <- div_names
  pred_combine <- model_ %>%
    map_dfc(.f = ~ .x$data_remain$fit)
  y_remain <- train_response[!id_shuffle]
  pred_test <- div_names %>%
    map_dfc(.f = ~ localPredict(model_[[.x]],
                                test_input))
  names(pred_test) <- names(pred_combine) <- div_names
  # C step: Consensual regression aggregation method with kernel-based COBRA
  list_method_agg <- list(mixcobra = function(pred){MixCobraReg(train_input = train_input[!id_shuffle,],
                                                                train_response = y_remain,
                                                                test_input = test_input,
                                                                train_predictions = pred,
                                                                test_predictions = pred_test,
                                                                test_response = test_response,
                                                                scale_input = K_step$scale_input,
                                                                scale_machine = C_step$scale_features,
                                                                n_cv = C_step$n_cv,
                                                                inv_sigma = inv_sigma,
                                                                alp = alp,
                                                                kernels = C_step$kernels,
                                                                optimizeMethod = C_step$opt_methods,
                                                                setGradParam = setGradParamMix,
                                                                setGridParam = setGridParamMix)},
                          cobra = function(pred){kernelAggReg(train_design = pred,
                                                              train_response = y_remain,
                                                              test_design = pred_test,
                                                              test_response = test_response,
                                                              scale_input = K_step$scale_input,
                                                              scale_machine = C_step$scale_features,
                                                              build_machine = FALSE,
                                                              machines = NULL,
                                                              n_cv = C_step$n_cv,
                                                              inv_sigma = sqrt(.5),
                                                              alp = 2,
                                                              kernels = C_step$kernels,
                                                              optimizeMethod = C_step$opt_methods,
                                                              setGradParam = setGradParamAgg,
                                                              setGridParam = setGridParamAgg)})
  res <- map(.x = C_step$method,
             .f = ~ list_method_agg[[.x]](pred_combine))
  list_agg_methods <- list(cobra = "cob",
                           mixcobra = "mix")
  names(res) <- C_step$method
  ext_fun <- function(L, nam){
    tab <- L$fitted_aggregate
    names(tab) <- paste0(names(tab), "_", nam)
    return(tab)
  }
  pred_fin <- C_step$method %>%
    map_dfc(.f = ~ ext_fun(res[[.x]], list_agg_methods[[.x]]))
  time.taken <- Sys.time() - start_time
  ### To finish
  if(is.null(test_response)){
    return(list(
    predict_final = pred_fin,
    predict_local = pred_test,
    agg_method = res,
    running_time = time.taken
  ))
  } else{
    error <- cbind(pred_test, pred_fin) %>%
      dplyr::mutate(y_test = test_response) %>%
      dplyr::summarise_all(.funs = ~ (. - y_test)) %>%
      dplyr::select(-y_test) %>%
      dplyr::summarise_all(.funs = ~ mean(.^2))
    return(list(
      predict_final = pred_fin,
      predict_local = pred_test,
      agg_method = res,
      mse = error,
      running_time = time.taken
  ))
  }
}

Example.4: A complete KFC procedure is implemented on the same Abalone data, using \(6\) BDs "euclidean", "itakura", "gkl", "logistic" and "polynomial" (of degree \(3\) and \(6\)). Both aggregation methods are used in the step \(C\). Two kernel functions are used for each aggregation method: "gaussian" (with gradient descent algorithm) and "epanechnikov" (with grid search algorithm).

train1 <- logical(n)
train1[sample(n,  floor(n*0.8))] <- TRUE
kfc1 <- KFCreg(train_input = df[train1,2:ncol(df)],
                train_response = df$Rings[train1],
                test_input = df[!train1,2:ncol(df)],
                K_step = stepK(K = 3,
                               scale_input = TRUE,
                               div = c("eucl", "ita", "gkl", "log" ,"poly"),
                               deg = c(3, 6),
                               splits = .5),
                C_step = stepC(method = c("cobra", "mixcobra"),
                               opt_methods = c("grad", "grid"),
                               kernels = c("gaussian", "gaussian"),
                               scale_features = FALSE),
                setGradParamAgg = setGradParameter(rate = 1),
                setGridParamAgg = setGridParameter(min_val = .00001,
                                                   max_val = 10,
                                                   n_val = 100),
                setGradParamMix = setGradParameter_Mix(rate = c(1,1)),
                setGridParamMix = setGridParameter_Mix(min_alpha = 1e-10,
                                                       max_alpha = 5,
                                                       min_beta = 1e-10,
                                                       max_beta = 10,
                                                       n_alpha = 20,
                                                       n_beta = 20))


Kernel-based consensual aggregation method
------------------------------------------

* Gradient descent algorithm ...
  Step  |  Parameter    |  Gradient |  Threshold 
 ---------------------------------------------------
   0    |  23.3334  |  -2.75e-11    |  1e-10 
 ---------------------------------------------------
   1    |  24.3334  |  -1.742e-10   |  0.0428571 

   2    |  30.6667  |  1.467e-10    |  0.2602737 

   3    |  25.3334  |  -1.1e-10     |  0.1739129 

   4    |  29.3334  |  2.567e-10    |  0.1578946 

   5    |  20.0000  |  -2.75e-11    |  0.3181816 

   6    |  21.0000  |  2.108e-10    |  0.04999994 

   7    |  13.3334  |  1.1e-10  |  0.365079 

   8    |  11.3334  |  0e+00    |  0.1499998 
-------------------------------------------------------
 Stopped|  11.3334  |  0        |  0
 ~ Observed parameter: 11.33336  in 8 iterations.
* Grid search algorithm... 
 ~ Observed parameter : 7.47475

MixCobra for regression
-----------------------

* Gradient descent algorithm ...
  Step  |  alpha    ;  beta     |  Gradient (alpha ; beta)  |  Threshold 
 --------------------------------------------------------------------------------
   0    |  7.50002  ;  37.52500     |  -2.292e-10  ;  -1.1e-10      |  1e-10 
 --------------------------------------------------------------------------------
   1    |  8.5000  ;  38.5250   |  -1.467e-10  ;  0e+00     |  0.04441974 

   2    |  9.1400  ;  38.5250   |  -1.375e-10  ;  -2.75e-11     |  0.01360977 

   3    |  9.7400  ;  38.7750   |  2.75e-11  ;  -3.667e-11  |  0.01783278 

   4    |  9.6200  ;  39.1083   |  -2.75e-11  ;  -1.833e-10     |  0.009344184 

   5    |  9.7400  ;  40.7750   |  -9.167e-11  ;  -8.25e-11     |  0.03666585 

   6    |  10.1400  ;  41.5250  |  -1.1e-10  ;  1.1e-10     |  0.0227655 

   7    |  10.6200  ;  40.5250  |  -2.75e-11  ;  2.75e-11   |  0.02864607 

   8    |  10.7400  ;  40.4000  |  3.667e-11  ;  -2.292e-10     |  0.0047903 

   9    |  10.5800  ;  41.4417  |  0e+00  ;  1.375e-10  |  0.02349758 

   10   |  10.5800  ;  41.1292  |  -1.375e-10  ;  -3.759e-10    |  0.00600711 

   11   |  10.7300  ;  41.5562  |  1.1e-10  ;  2.017e-10    |  0.01116017 

   12   |  10.6700  ;  41.4417  |  -1.1e-10  ;  0e+00   |  0.00333899 

   13   |  10.7000  ;  41.4417  |  -1.192e-10  ;  -2.75e-11     |  0.0005756866 

   14   |  10.7163  ;  41.4456  |  -1.1e-10  ;  0e+00   |  0.0003865669 

   15   |  10.7313  ;  41.4456  |  -2.75e-11  ;  3.667e-11  |  0.0002875665 

   16   |  10.7350  ;  41.4443  |  -1.1e-10  ;  -1.1e-10    |  9.682615e-05 

   17   |  10.7500  ;  41.4462  |  1.192e-10  ;  2.108e-10  |  0.0003249014 

   18   |  10.7338  ;  41.4444  |  0e+00  ;  -9.167e-11     |  0.0003471848 

   19   |  10.7338  ;  41.4448  |  -1.558e-10  ;  -1.1e-10  |  7.798307e-06 

   20   |  10.7391  ;  41.4450  |  -3.667e-11  ;  5.5e-11   |  0.0001064928 

   21   |  10.7397  ;  41.4449  |  9.167e-11  ;  2.75e-11   |  1.431605e-05 

   22   |  10.7382  ;  41.4449  |  0e+00  ;  -1.833e-10     |  3.052659e-05 

   23   |  10.7382  ;  41.4451  |  -1.1e-10  ;  -3.667e-11  |  3.898789e-06 

   24   |  10.7386  ;  41.4451  |  -1.375e-10  ;  9.167e-11     |  9.372653e-06 

   25   |  10.7389  ;  41.4450  |  -1.192e-10  ;  -3.667e-11    |  6.588866e-06 

   26   |  10.7392  ;  41.4450  |  -1.192e-10  ;  3.667e-11     |  5.060538e-06 

   27   |  10.7394  ;  41.4450  |  1.192e-10  ;  2.75e-11   |  4.963045e-06 

   28   |  10.7392  ;  41.4450  |  -3.667e-11  ;  6.417e-11     |  4.902104e-06 

   29   |  10.7392  ;  41.4450  |  2.75e-11  ;  7.334e-11   |  8.338345e-07 

   30   |  10.7392  ;  41.4450  |  2.75e-11  ;  1.192e-10   |  3.781737e-07 

   31   |  10.7392  ;  41.4450  |  -1.1e-10  ;  -9.167e-11  |  2.987378e-07 

   32   |  10.7392  ;  41.4450  |  -1.467e-10  ;  -1.467e-10    |  6.832472e-07 

   33   |  10.7392  ;  41.4450  |  -1.1e-10  ;  3.667e-11   |  4.717424e-07 

   34   |  10.7392  ;  41.4450  |  -9.167e-11  ;  2.75e-11  |  3.050729e-07 

   35   |  10.7393  ;  41.4450  |  -1.558e-10  ;  -2.108e-10    |  2.430593e-07 

   36   |  10.7393  ;  41.4450  |  3.667e-11  ;  0e+00  |  4.677214e-07 

   37   |  10.7393  ;  41.4450  |  1.375e-10  ;  1.467e-10  |  9.356861e-08 

   38   |  10.7393  ;  41.4450  |  -1.192e-10  ;  -3.667e-11    |  1.876246e-07 

   39   |  10.7393  ;  41.4450  |  0e+00  ;  0e+00  |  1.53572e-07 

   40   |  10.7393  ;  41.4450  |  0e+00  ;  0e+00  |  1.558412e-10 
--------------------------------------------------------------------------------
 Stopped|  10.7393  ;  41.4450  |     0         |  0
 ~ Observed parameter: (alpha, beta) = ( 10.73927 ,  41.44502 ) in 40 itertaions.

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (1e-10, 7.368421)

The mean square errors evaluated on \(20\%\)-testing data of the above computation are reported below.

kfc1$predict_final %>%
  mutate(rf = predict(rf1, newdata = df[!train1,2:ncol(df)])) %>%
  sweep(MARGIN = 1, STATS = df$Rings[!train1], FUN = "-") %>%
  .^2  %>%
  colMeans
gaussian_grad_cob gaussian_grid_cob gaussian_grad_mix gaussian_grid_mix                rf 
     6.453406e-28      2.144367e-20      1.803888e+00      5.635831e+00      4.603206e+00 

📖 Read also KernelAggReg and MixCobraReg.



LS0tDQp0aXRsZTogIjxzcGFuIHN0eWxlPSdjb2xvcjogIzFDODFBQTsnPioqS0ZDIHByb2NlZHVyZSBmb3IgcmVncmVzc2lvbiAtPC9zcGFuPiBbSGFzIGV0IGFsLiAoMjAyMSldKGh0dHBzOi8vd3d3LnRhbmRmb25saW5lLmNvbS9lcHJpbnQvWUtHUzhHVEtEQktZRlhFR0ZXU0IvZnVsbD90YXJnZXQ9MTAuMTA4MC8wMDk0OTY1NS4yMDIxLjE4OTE1MzkpKioiDQphdXRob3I6ICI8c3BhbiBzdHlsZT0nY29sb3I6ICNENEE1MUM7Jz4qKipTb3RoZWEgSGFzKioqPC9zcGFuPiINCmRhdGU6ICI1LzIwLzIwMjIiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY3NzOiBoaWRlT3V0cHV0LmNzcw0KICAgIGluY2x1ZGVzOg0KICAgICAgaW5faGVhZGVyOiBoaWRlT3V0cHV0LnNjcmlwdA0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6ICcyJw0KICAgIHRvY2RlcHRoOiAyDQogIGh0bWxfbm90ZWJvb2s6DQogICAgY3NzOiBoaWRlT3V0cHV0LmNzcw0KICAgIGluY2x1ZGVzOg0KICAgICAgaW5faGVhZGVyOiBoaWRlT3V0cHV0LnNjcmlwdA0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDINCiAgICB0b2NkZXB0aDogMg0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6ICcyJw0KLS0tDQoNCjxzdHlsZT4NCiAgLmJ0biB7DQogICAgYm9yZGVyLXdpZHRoOiAwIDBweCAwcHggMHB4Ow0KICAgIGZvbnQtd2VpZ2h0OiBub3JtYWw7DQogICAgdGV4dC10cmFuc2Zvcm06IDsNCiAgfQ0KLmJ0bi1kZWZhdWx0IHsNCiAgY29sb3I6ICMyZWNjNzE7DQogICAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZmZmZjsNCiAgICBib3JkZXItY29sb3I6ICNmZmZmZmY7DQp9DQo8L3N0eWxlPg0KDQo8IS0tIENvbG9ycw0KYmx1ZSA6ICMxRkFBRTMNCnllbGxvdyA6ICNGMEFFMTQNCmdyZWVuIDogIzU0RDMxOSANCnJlZCA6ICNFNjE4MEENCi0tPg0KDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KIyBDaGVjayBpZiBwYWNrYWdlICJmb250YXdlc29tZSIgaXMgYWxyZWFkeSBpbnN0YWxsZWQgDQoNCmxvb2t1cF9wYWNrYWdlcyA8LSBpbnN0YWxsZWQucGFja2FnZXMoKVssMV0NCmlmKCEoImZvbnRhd2Vzb21lIiAlaW4lIGxvb2t1cF9wYWNrYWdlcykpDQogIGluc3RhbGwucGFja2FnZXMoImZvbnRhd2Vzb21lIikNCmBgYA0KDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPiYjMTI4MjcwOzx1PiBIb3cgdG8gZG93bmxvYWQgJiBydW4gdGhlIGNvZGVzPzwvdT48L3NwYW4+ey19DQo9PT0NCg0KQWxsIHRoZSBzb3VyY2UgY29kZXMgb2YgdGhlIGFnZ3JlZ2F0aW9uIG1ldGhvZHMgYXJlIGF2YWlsYWJsZSBbaGVyZSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPiBgciBmb250YXdlc29tZTo6ZmEoImdpdGh1YiIpYDwvc3Bhbj5dKGh0dHBzOi8vZ2l0aHViLmNvbS9oYXNzb3RoZWEvQWdncmVnYXRpb25NZXRob2RzKS4gVG8gcnVuIHRoZSBjb2RlcywgeW91IGNhbiA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPmBjbG9uZWA8L3NwYW4+IHRoZSByZXBvc2l0b3J5IGRpcmVjdGx5IG9yIHNpbXBseSBsb2FkIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPmBSIHNjcmlwdGA8L3NwYW4+IHNvdXJjZSBmaWxlIGZyb20gdGhlIHJlcG9zaXRvcnkgdXNpbmcgW2RldnRvb2xzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZGV2dG9vbHMvaW5kZXguaHRtbCkgcGFja2FnZSBpbiA8c3BhbiBzdHlsZT0iY29sb3I6ICMwMjg3RDg7Ij4gKipSc3R1ZGlvKiogPC9zcGFuPiBhcyBmb2xsb3c6DQoNCjEuIEluc3RhbGwgW2RldnRvb2xzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZGV2dG9vbHMvaW5kZXguaHRtbCkgcGFja2FnZSB1c2luZyBjb21tYW5kOiANCg0KICAgIGBpbnN0YWxsLnBhY2thZ2VzKCJkZXZ0b29scyIpYA0KDQoyLiBMb2FkaW5nIHRoZSBzb3VyY2UgY29kZXMgZnJvbSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPkdpdEh1YiBgciBmb250YXdlc29tZTo6ZmEoImdpdGh1YiIpYDwvc3Bhbj4gcmVwb3NpdG9yeSB1c2luZyBgc291cmNlX3VybGAgZnVuY3Rpb24gYnk6IA0KDQogICAgYGRldnRvb2xzOjpzb3VyY2VfdXJsKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vaGFzc290aGVhL0FnZ3JlZ2F0aW9uTWV0aG9kcy9tYWluL0tlcm5lbEFnZ1JlZy5SIilgDQoNCi0tLQ0KDQo+ICoqJiM5OTk4OyBOb3RlKio6IEFsbCBjb2RlcyBjb250YWluZWQgaW4gdGhpcyBgUm1hcmtkb3duYCBhcmUgYnVpbHQgd2l0aCByZWNlbnQgdmVyc2lvbiBvZiA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPmByIGZvbnRhd2Vzb21lOjpmYSgici1wcm9qZWN0IilgPC9zcGFuPiAodmVyc2lvbiAkPiQgNC4xLCBhdmFpbGFibGUgW2hlcmVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL2Jpbi93aW5kb3dzL2Jhc2UvKSkgYW5kIDxzcGFuIHN0eWxlPSJjb2xvcjogIzAyODdEODsiPiAqKlJzdHVkaW8qKiA8L3NwYW4+ICh2ZXJzaW9uID4gYDIwMjIuMDIuMis0ODVgLCBhdmFpbGFibGUgW2hlcmVdKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Byb2R1Y3RzL3JzdHVkaW8vZG93bmxvYWQvI2Rvd25sb2FkKSkuIE5vdGUgYWxzbyB0aGF0IHRoZSBjb2RlIGNodWNrcyBhcmUgPHNwYW4gc3R5bGU9ImNvbG9yOiAjRTYxODBBOyI+aGlkZGVuPC9zcGFuPiBieSBkZWZhdWx0Lg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQiPiAqKlRvIHNlZSB0aGUgY29kZXMsIHlvdSBjYW46KiogPC9zcGFuPg0KDQotIGNsaWNrIG9uIHRoZSB0b3AtcmlnaHQgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNTREMzE5IDsiPmBDb2RlYDwvc3Bhbj4gYnV0dG9uIG9mIHRoZSBwYWdlLCB0aGVuIGNob29zZSAqKlNob3cgQWxsIENvZGUqKiB0byBzaG93IGFsbCB0aGUgY29kZXMsIG9yIA0KLSBzaW1wbHkgY2xpY2sgb24gdGhlIHJpZ2h0LWNvcm5lciA8c3BhbiBzdHlsZT0iY29sb3I6ICM1NEQzMTkgOyI+YENvZGVgPC9zcGFuPiBidXR0b24gYXQgZWFjaCBzZWN0aW9uIHRvIHNob3cgdGhlIGNvZGVzIG9mIHRoYXQgc3BlY2lmaWMgc2VjdGlvbi4NCg0KLS0tDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPjx1PktGQyBwcm9jZWR1cmUgJiBpbXBvcnRhbnQgcGFja2FnZXMgPC91Pjwvc3Bhbj4NCj09PQ0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij48dT5LRkMgcHJvY2VkdXJlPC91Pjwvc3Bhbj4NCi0tLQ0KDQpLRkMgcHJvY2VkdXJlIGlzIGEgdGhyZWUtc3RlcCBtZXRob2RvbG9neSB3aGljaCBwdXRzIHRvZ2V0aGVyIGNsdXN0ZXJpbmcgYW5kIGNvbnNlbnN1YWwgYWdncmVnYXRpb24gbWV0aG9kcyB0byBjb25zdHJ1Y3QgcHJlZGljdGlvbnMgaW4gc3VwZXJ2aXNlZCBsZWFybmluZyBwcm9ibGVtcy4gVGhlIHRocmVlIHN0ZXBzIG9mIHRoZSBwcm9jZWR1cmUgYXJlOg0KDQotICoqU3RlcCAqSyogKio6ICRLJC1tZWFucyBjbHVzdGVyaW5nIGFsZ29yaXRobSBpcyBpbXBsZW1lbnRlZCBvbiB0aGUgaW5wdXQgZGF0YSB1c2luZyBzZXZlcmFsIG9wdGlvbnMgb2YgQnJlZ21hbiBkaXZlcmdlbmNlcyAkKHtcY2FsIEJ9X2opX3tqPTF9Xk0kICgkTSQgaXMgdGhlIG51bWJlciBvZiB0b3RhbCBkaXZlcmdlbmNlcyB1c2VkKSwgdGhlcmVmb3JlLCB0aGUgaW5wdXQgZGF0YSBpcyBwYXJ0aXRpb25lZCBpbnRvIG1hbnkgZGlmZmVyZW50IHN0cnVjdHVyZXMsIGFjY29yZGluZyB0byB0aGUgQnJlZ21hbiBkaXZlcmdlbmNlIHVzZWQuDQotICoqU3RlcCAqRiogKio6IEZvciBhIHBhcnRpdGlvbiBzdHJ1Y3R1cmUgZ2l2ZW4gYnkgYSBkaXZlcmdlbmNlICR7XGNhbCBCfV9qJCwgd2UgZml0IHNpbXBsZSBtb2RlbHMgKGxpbmVhciwgZm9yIGV4YW1wbGUpIG9uIGFsbCB0aGUgY2x1c3RlcnMgb2YgdGhlIG9idGFpbmVkIHBhcnRpdGlvbi4gVGhlbiwgdGhlIGNvbGxlY3Rpb24gJHtcY2FsIE19X2o9XHt7XGNhbCBNfV97aixrfVx9X3trPTF9XkskIG9mIHRoZXNlIGxvY2FsIG1vZGVscyBpcyBjYWxsZWQgKmNhbmRpZGF0ZSogbW9kZWwsIGNvcnJlc3BvbmRpbmcgdG8gdGhlIEJyZWdtYW4gZGl2ZXJnZW5jZSAke1xjYWwgQn1faiQuIEF0IHRoZSBlbmQgb2YgdGhpcyBzdGVwLCBzZXZlcmFsIGNhbmRpZGF0ZSBtb2RlbHMgYXJlIGNvbnN0cnVjdGVkLiANCi0gKipTdGVwICpDKiAqKjogVGhpcyBzdGVwIGFnZ3JlZ2F0ZXMgdGhlIG9idGFpbmVkIGNhbmRpZGF0ZSBtb2RlbHMgdXNpbmcgY29uc2Vuc3VhbCBhZ2dyZWdhdGlvbiBtZXRob2RzIHN0dWRpZWQgaW4gW0hhcyAoMjAyMSldKGh0dHBzOi8vaGFsLmFyY2hpdmVzLW91dmVydGVzLmZyL2hhbC0wMjg4NDMzM3Y1KSBvciBbRmlzY2hlciBhbmQgTW91Z2VvdCAoMjAxOSldKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzAzNzgzNzU4MTgzMDIzNDkpLg0KDQoNCi0tLQ0KDQo+KipSZW1hcmsuMSoqOiANClRoZSBwcmVkaWN0aW9uIG9mIGFueSBvYnNlcnZhdGlvbiAkeCQgZ2l2ZW4gYnkgYSBjYW5kaWRhdGUgbW9kZWwgJHtcY2FsIE19X2okIGlzIGRvbmUgaW4gdHdvIHNpbXBsZSBzdGVwczoNCg0KMS4gJHgkIGlzIGNsYXNzaWZpZWQgaW50byBvbmUgb2YgdGhlIG9idGFpbmVkIGNsdXN0ZXJzIHVzaW5nIHRoZSBjb3JyZXNwb25kaW5nIGRpdmVyZ2VuY2UgJHtcY2FsIEJ9X2okLCBpLmUuLA0KICAkJHhcaW57XGNhbCBDfV97a14qfSBcTGVmdHJpZ2h0YXJyb3cge1xjYWwgQn1faihjX3trXip9LHgpPVxpbmZfezFcbGVxIGtcbGVxIEt9e1xjYWwgQn1faihjX2sseCkkJA0Kd2hlcmUgJFx7Y18xLC4uLixjX0tcfV97az0xfV5LJCBhcmUgdGhlIGNlbnRyb2lkcyBvZiB0aGUgY29ycmVzcG9uZGluZyBjbHVzdGVycyAkXHtDXzEsLi4uLENfS1x9JC4NCg0KMi4gVGhlIHByZWRpY3Rpb24gb2YgJHgkIGlzIGdpdmVuIGJ5IHRoZSBjb3JyZXNwb25kaW5nIGxvY2FsIG1vZGVsICR7XGNhbCBNfV97aixrXip9JCBkZWZpbmVkIG9uIGNsdXN0ZXIgJGteKiQsIGkuZS4sICR7XGNhbCBNfV9qKHgpPXtcY2FsIE19X3tqLGteKn0oeCkkLg0KDQotLS0NCg0KIVtUaGUgZmlndXJlIGFib3ZlIHJlcHJlc2VudHMgdGhlIHN1bW1hcnkgb2YgS0ZDIHByb2NlZHVyZV0oLi9rZmMucG5nKQ0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij48dT5JbXBvcnRhbnQgcGFja2FnZXM8L3U+PC9zcGFuPg0KLS0tDQoNCldlIHByZXBhcmUgYWxsIHRoZSBuZWNlc3NhcnkgdG9vbHMgZm9yIHRoaXMgYFJtYXJrZG93bmAuIGBwYWNtYW5gIHBhY2thZ2UgYWxsb3dzIHVzIHRvIGxvYWQgKGlmIGV4aXN0cykgb3IgaW5zdGFsbCAoaWYgZG9lcyBub3QgZXhpc3QpIGFueSBhdmFpbGFibGUgcGFja2FnZXMgZnJvbSBbVGhlIENvbXByZWhlbnNpdmUgUiBBcmNoaXZlIE5ldHdvcmsgKENSQU4pXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy8pIG9mIDxzcGFuIHN0eWxlPSJjb2xvcjogIzA5N0JDMSI+YHIgZm9udGF3ZXNvbWU6OmZhKCJyLXByb2plY3QiKWA8L3NwYW4+LiANCg0KDQpgYGB7cn0NCiMgQ2hlY2sgaWYgcGFja2FnZSAicGFjbWFuIiBpcyBhbHJlYWR5IGluc3RhbGxlZCANCg0KbG9va3VwX3BhY2thZ2VzIDwtIGluc3RhbGxlZC5wYWNrYWdlcygpWywxXQ0KaWYoISgicGFjbWFuIiAlaW4lIGxvb2t1cF9wYWNrYWdlcykpDQogIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpDQoNCg0KIyBUbyBiZSBpbnN0YWxsZWQgb3IgbG9hZGVkDQpwYWNtYW46OnBfbG9hZChtYWdyaXR0cikNCnBhY21hbjo6cF9sb2FkKHRpZHl2ZXJzZSkNCg0KIyMgcGFja2FnZSBmb3IgImdlbmVyYXRlTWFjaGluZXMiDQpwYWNtYW46OnBfbG9hZCh0cmVlKQ0KcGFjbWFuOjpwX2xvYWQoZ2xtbmV0KQ0KcGFjbWFuOjpwX2xvYWQocmFuZG9tRm9yZXN0KQ0KcGFjbWFuOjpwX2xvYWQoRk5OKQ0KcGFjbWFuOjpwX2xvYWQoeGdib29zdCkNCnBhY21hbjo6cF9sb2FkKGtlcmFzKQ0KcGFjbWFuOjpwX2xvYWQocHJhY21hKQ0KcGFjbWFuOjpwX2xvYWQobGF0ZXgyZXhwKQ0KcGFjbWFuOjpwX2xvYWQocGxvdGx5KQ0KcGFjbWFuOjpwX2xvYWQocGFyYWxsZWwpDQpwYWNtYW46OnBfbG9hZChmb3JlYWNoKQ0KcGFjbWFuOjpwX2xvYWQoZG9QYXJhbGxlbCkNCnJtKGxvb2t1cF9wYWNrYWdlcykNCmBgYA0KDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPjx1PkJyZWdtYW4gZGl2ZXJnZW5jZXMgKEJEKTwvdT48L3NwYW4+DQo9PT0NCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+KipEZWZpbml0aW9uKio8L3NwYW4+IExldCAkXHBoaTpcbWF0aGNhbHtDfVxyaWdodGFycm93XG1hdGhiYntSfSQgYmUgYSBzdHJpY3RseSBjb252ZXggYW5kIGNvbnRpbnVvdXNseSBkaWZmZXJlbnRpYWJsZSBmdW5jdGlvbiBkZWZpbmVkIG9uIGEgbWVhc3VyYWJsZSBjb252ZXggc3Vic2V0ICRcbWF0aGNhbHtDfVxzdWJzZXRcbWF0aGJie1J9XmQkLiBMZXQgJGludChcbWF0aGNhbHtDfSkkIGRlbm90ZSBpdHMgcmVsYXRpdmUgaW50ZXJpb3IuIEEgQnJlZ21hbiBkaXZlcmdlbmNlIGluZGV4ZWQgYnkgJFxwaGkkIGlzIGEgZGlzc2ltaWxhcml0eSBtZWFzdXJlICRkX3tccGhpfTpcbWF0aGNhbHtDfVx0aW1lcyBpbnQoXG1hdGhjYWx7Q30pXHJpZ2h0YXJyb3dcbWF0aGJie1J9JCBkZWZpbmVkIGZvciBhbnkgcGFpciAkKHgseSlcaW4gXG1hdGhjYWx7Q31cdGltZXMgaW50KFxtYXRoY2Fse0N9KSQgYnksDQpcYmVnaW57ZXF1YXRpb259DQpcbGFiZWx7ZXE6MS4xMH0NCmRfe1xwaGl9KHgseSk9XHBoaSh4KS1ccGhpKHkpLVxsYW5nbGUgeC15LFxuYWJsYVxwaGkoeSlccmFuZ2xlIA0KXGVuZHtlcXVhdGlvbn0NCndoZXJlICRcbmFibGFccGhpKHkpJCBkZW5vdGVzIHRoZSBncmFkaWVudCBvZiAkXHBoaSQgY29tcHV0ZWQgYXQgYSBwb2ludCAkeVxpbiBpbnQoXG1hdGhjYWx7Q30pJC4gQSBCcmVnbWFuIGRpdmVyZ2VuY2UgaXMgbm90IG5lY2Vzc2FyaWx5IGEgbWV0cmljIGFzIGl0IG1heSBub3QgYmUgc3ltbWV0cmljIGFuZCB0aGUgdHJpYW5ndWxhciBpbmVxdWFsaXR5IG1pZ2h0IG5vdCBiZSBzYXRpc2ZpZWQuDQoNClRoaXMgc2VjdGlvbiBkZWZpbmVzIGFsbCB0aGUgQnJlZ21hbiBkaXZlcmdlbmNlcyB1c2VkLiBUaGUgbGlzdCBvZiBhbGwgdGhlIEJyZWdtYW4gZGl2ZXJnZW5jZXMgaXMgZ2l2ZW4gaW4gdGhlIHRhYmxlIGJlbG93Og0KDQotLS0tDQoNCk5hbWUgICAgICAgICAgICAgICAgICAgICAgICAkXHBoaSQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJGRfe1xwaGl9JCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAkXGNhbCBDJA0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAtLS0tLS0tLS0tLQ0KRXVjbGlkZWFuICAgICAgICAgICAgICAgICAke1x8eFx8XzJeMn09XHN1bV97aT0xfV5keF9pXjIkICAgICAgICAgICAgICAgICAgICRcfHgteVx8XzJeMiQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAkXG1hdGhiYntSfV5kJA0KR2VuZXJhbCBLdWxsYmFjay1MZWlibGVyICAkXHN1bV97aT0xfV5kIHhfaVxsbiggeF9pKSQgICAgICAgICAgICAgICAgICAgICAgICRcc3VtX3tpPTF9XmQoIHhfaVxsbihcZnJhY3sgeF9pfXt5X2l9KS0oeF9pLXlfaSkpJCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAkKDAsK1xpbmZ0eSleZCQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQpMb2dpc3RpYyAgICAgICAgICAgICAgICAgICRcc3VtX3tpPTF9XmQoeF9pXGxuKCB4X2kpKygxLSB4X2kpXGxuKDEtIHhfaSkpJCAgJFxzdW1fe2k9MX1eZFxCaWcoIHhfaVxsbihcZnJhY3t4X2l9e3lfaX0pKygxLSB4X2kpXGxuKFxmcmFjezEtIHhfaX17MS15X2l9KVxCaWcpJCAgICQoMCwxKV5kJA0KSXRha3VyYS1TYWl0byAgICAgICAgICAgICAkLVxzdW1fe2k9MX1eZFxsbiggeF9pKSQgICAgICAgICAgICAgICAgICAgICAgICAgICRcc3VtX3tpPTF9XmRcQmlnKFxmcmFjeyB4X2l9e3lfaX0tXGxuKFxmcmFjeyB4X2l9e3lfaX0pLTFcQmlnKSQgICAgICAgICAgICAgICAgICAgICQoMCwrXGluZnR5KV5kJA0KRXhwb25lbnRpYWwgICAgICAgICAgICAgICAkXHN1bV97aT0xfV5kZV57eF9pfSQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICRcc3VtX3tpPTF9XmQoZV57eF9pfS1lXnt5X2l9LWVee3lfaX0oeF9pLXlfaSkpJCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICRcbWF0aGJie1J9XmQkDQpQb2x5bm9taWFsICAgICAgICAgICAgICAgICRcc3VtX3tpPTF9XmR8eHxecCxwPjIkICAgICAgICAgICAgICAgICAgICAgICAgICAgJFxzdW1fe2s9MX1eZCh8eF9rfF5wLXx5X2t8XnAtXHRleHR7c2lnbn0oeV9rKV5wcCh4X2steV9rKXlfa157cC0xfSkkICAgICAgICAgICAgICRcbWF0aGJie1J9XmQkDQoNCi0tLS0NCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+TG9vay11cCBsaXN0IG9mIEJyZWdtYW4gZGl2ZXJnZW5jZXM8L3U+PC9zcGFuPg0KLS0tLQ0KDQpUaGUgY29kZXMgYmVsb3cgcHJvdmlkZSBhIGxvb2stdXAgbGlzdCBvZiBhbGwgdGhlIEJEcyBkZWZpbmVkIGluIHRoZSB0YWJsZSBhYm92ZS4NCg0KYGBge3J9DQpldWNsaWREaXYgPC0gZnVuY3Rpb24oWC4sIHkuLCBkZWcgPSBOVUxMKXsNCiAgICByZXMgPC0gc3dlZXAoWC4sIDIsIHkuKQ0KICAgIHJldHVybihyb3dTdW1zKHJlc14yKSkNCn0NCmdrbERpdiA8LSBmdW5jdGlvbihYLiwgeS4sIGRlZyA9IE5VTEwpew0KICByZXMgPC0gYygiLyIsICItIikgJT4lDQogICAgbWFwKC5mID0gfiBzd2VlcChYLiwgMiwgeS4sIEZVTiA9IC54KSkNCiAgcmV0dXJuKHJvd1N1bXMoWC4qbG9nKHJlc1tbMV1dKSAtIHJlc1tbMl1dKSkNCn0NCmxvZ0RpdiA8LSBmdW5jdGlvbihYLiwgeS4sIGRlZyA9IE5VTEwpew0KICAgIHJlcyA8LSAgbWFwMigueCA9IGxpc3QoWC4sIDEtWC4pLA0KICAgICAgICAgICAgICAgICAueSA9IGxpc3QoeS4sIDEteS4pLA0KICAgICAgICAgICAgICAgICAuZiA9IH4gc3dlZXAoLngsIDIsIC55LCBGVU4gPSAiLyIpKQ0KICAgIHJldHVybihyb3dTdW1zKFguKmxvZyhyZXNbWzFdXSkrKDEtWC4pKmxvZyhyZXNbWzJdXSkpKQ0KfQ0KaXRhRGl2IDwtIGZ1bmN0aW9uKFguLCB5LiwgZGVnID0gTlVMTCl7DQogICAgcmVzIDwtIHN3ZWVwKFguLCAyLCB5LiwgRlVOID0gIi8iKQ0KICAgIHJldHVybihyb3dTdW1zKHJlcy1sb2cocmVzKSAtIDEpKQ0KfQ0KZXhwRGl2IDwtIGZ1bmN0aW9uKFguLCB5LiwgZGVnID0gTlVMTCl7DQogICAgZXhwX3kgPC0gZXhwKHkuKQ0KICAgIHJlcyA8LSBzd2VlcCgxK1guLCAyLCB5LikgJT4lDQogICAgICBzd2VlcCgyLCBleHBfeSwgRlVOID0gIioiKQ0KICAgIHJldHVybihyb3dTdW1zKGV4cChYLiktcmVzKSkNCn0NCnBvbHlEaXYgPC0gZnVuY3Rpb24oWC4sIHkuLCBkZWcgPSAzKXsNCiAgICBTIDwtIG1hcDIoLnggPSBsaXN0KFguLCBYLl5kZWcpLA0KICAgICAgICAgICAgICAueSA9IGxpc3QoeS4sIHkuXmRlZyksDQogICAgICAgICAgICAgIC5mID0gfiBzd2VlcCgueCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBNQVJHSU4gPSAyLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIFNUQVRTID0gLnksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBGVU4gPSAiLSIpKQ0KICAgIGlmKGRlZyAlJSAyID09IDApew0KICAgICAgVGVtIDwtIHN3ZWVwKFNbWzFdXSwgMiwgeS5eKGRlZy0xKSwgRlVOID0gIioiKQ0KICAgICAgcmVzIDwtIHJvd1N1bXMoU1tbMl1dIC0gZGVnICogVGVtKQ0KICAgIH0NCiAgICBlbHNlew0KICAgICAgVGVtIDwtIHN3ZWVwKFNbWzFdXSwgMiwgc2lnbih5LikgKiB5Ll4oZGVnLTEpLCBGVU4gPSAiKiIpDQogICAgICByZXMgPC0gcm93U3VtcyhTW1syXV0gLSBkZWcgKiBUZW0pDQogICAgfQ0KICAgIHJldHVybihyZXMpDQp9DQpsb29rdXBfZGl2IDwtIGxpc3QoDQogIGV1Y2xpZGVhbiA9IGV1Y2xpZERpdiwNCiAgZ2tsID0gZ2tsRGl2LA0KICBsb2dpc3RpYyA9IGxvZ0RpdiwNCiAgaXRha3VyYSA9IGl0YURpdiwNCiAgZXhwb25lbnRpYWwgPSBleHBEaXYsDQogIHBvbHlub21pYWwgPSBwb2x5RGl2DQopDQpgYGANCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij48dT5GdW5jdGlvbjwvdT48L3NwYW4+IDogYEJyZWdtYW5EaXZgDQotLS0tDQoNClRoaXMgZnVuY3Rpb24gY29tcHV0ZXMgdGhlIG1hdHJpeCBvZiBCRHMgYmV0d2VlbiB0d28gc2V0cyBvZiBkYXRhIHBvaW50cy4gRWFjaCBzZXQgb2YgZGF0YSBwb2ludHMgc2hvdWxkIGJlIHN0b3JlZCBhcyBtYXRyaWNlcywgZGF0YSBmcmFtZSwgb3IgdGliYmxlIG9iamVjdCB3aGVyZSBlYWNoIHJvdyByZXByZXNlbnRzIGFuIGluZGl2aWR1YWwgZGF0YSBwb2ludC4NCg0KLSAqKkFyZ3VtZW50Kio6DQoNCiAgICAtIGBYLmAsIGBDLmAgOiBUaGUgZGF0YSBtYXRyaWNlcywgdGliYmxlcyBhbmQgZGF0YSBmcmFtZXMsIGNvbnRhaW5pbmcgdGhlIGRhdGEgcG9pbnRzIGZvciB3aGljaCB0aGUgQnJlZ21hbiBkaXZlcmdlbmNlcyBiZXR3ZWVuIHRoZW0gYXJlIHRvIGJlIGNvbXB1dGVkLg0KICAgIC0gYGRpdmAgOiB0aGUgdHlwZSBvZiBkaXZlcmdlbmNlIHRvIGJlIHVzZWQuIEl0IHNob3VsZCBiZSBhIHN1YnNldCBvZiBgeyJldWNsaWRlYW4iLCAiZ2tsIiwgImxvZ2lzdGljIiwgIml0YWt1cmEiLCAiZXhwb25lbnRpYWwiLCAicG9seW5vbWlhbCJ9YC4NCiAgICAtIGBkZWdgIDogdGhlIGRlZ3JlZSBvZiBwb2x5bm9taWFsIEJEIChpZiBvbmUgaXMgdXNlZCkuDQotICoqVmFsdWUqKjogDQogICAgDQogICAgVGhpcyBmdW5jdGlvbiByZXR1cm5zIGEgKnRpYmJsZSogb2JqZWN0ICREPShkX3tpLGp9KSQgd2hlcmUgJGRfe2ksan0kIGlzIHRoZSBCcmVnbWFuIGRpdmVyZ2VuY2UgYmV0d2VlbiByb3cgJGkkIG9mIGBYLmAgYW5kIHJvdyAkaiQgb2YgYEMuYC4NCg0KDQpgYGB7cn0NCkJyZWdtYW5EaXYgPC0gZnVuY3Rpb24oWC4sIA0KICAgICAgICAgICAgICAgICAgICAgICBDLiwgDQogICAgICAgICAgICAgICAgICAgICAgIGRpdiA9IGMoImV1Y2xpZGVhbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJna2wiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibG9naXN0aWMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaXRha3VyYSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJleHBvbmVudGlhbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJwb2x5bm9taWFsIiksDQogICAgICAgICAgICAgICAgICAgICAgIGRlZyA9IDMpew0KICBkaXYgPC0gbWF0Y2guYXJnKGRpdikNCiAgZF9jIDwtIGRpbShDLikNCiAgaWYoaXMubnVsbChkX2MpKXsNCiAgICBDIDwtIG1hdHJpeChDLiwgbnJvdyA9IDEsIGJ5cm93ID0gVFJVRSkNCiAgfSBlbHNlew0KICAgIEMgPC0gYXMubWF0cml4KEMuKQ0KICB9DQogIGlmKGlzLm51bGwoZGltKFguKSkpew0KICAgIFggPC0gbWF0cml4KFguLCBucm93ID0gMSwgYnlyb3cgPSBUUlVFKQ0KICB9IGVsc2V7DQogICAgWCA8LSBhcy5tYXRyaXgoWC4pDQogIH0NCiAgZGlzIDwtICBtYXBfZGZjKC54ID0gMTpkaW0oQylbMV0sDQogICAgICAgICAgICAgICAgICAuZiA9IH4gdGliYmxlKCd7ey54fX0nIDo9IGxvb2t1cF9kaXZbW2Rpdl1dKFgsIENbLngsXSwgZGVnID0gZGVnKSkpDQogIHJldHVybihkaXMpDQp9DQpgYGANCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+PHU+U3RlcCAkSyQ6ICRLJC1tZWFucyB3aXRoIEJyZWdtYW4gZGl2ZXJnZW5jZXM8L3U+PC9zcGFuPg0KPT09DQoNClRoaXMgc2VjdGlvbiBpbXBsZW1lbnRzICRLJC1tZWFucyBhbGdvcml0aG0gdXNpbmcgQnJlZ21hbiBkaXZlcmdlbmNlcyB3aGljaCBjb3JyZXNwb25kcyB0byB0aGUgc3RlcCAkSyQgb2YgS0ZDIHByb2NlZHVyZS4NCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+RnVuY3Rpb248L3U+PC9zcGFuPiA6IGBmaW5kQ2xvc2VzdENlbnRyb2lkYCBhbmQgYG5ld0NlbnRyb2lkc2ANCi0tLS0NCg0KVGhlc2UgdHdvIGZ1bmN0aW9ucyBwZXJmb3JtIHRoZSBtYWluIHN0ZXBzIG9mICRLJC1tZWFucyBhbGdvcml0aG0uIGBmaW5kQ2xvc2VzdENlbnRyb2lkYCBmdW5jdGlvbiBhc3NpZ25zIGFueSBkYXRhIHBvaW50cyB0byBzb21lIGNsdXN0ZXIgYWNjb3JkaW5nIHRvIHRoZSBuZWFyZXN0IGNlbnRyb2lkIGFtb25nIGFsbCB0aGUgY2VudHJvaWRzLCBhbmQgYG5ld0NlbnRyb2lkc2AgZnVuY3Rpb24gY29tcHV0ZSB0aGUgbmV3IGNlbnRyb2lkcyBvZiB0aGUgcGFydGl0aW9uIGdpdmVuIHRoZSBjbHVzdGVyIGxhYmVscyBvZiBhbGwgZGF0YSBwb2ludHMuDQoNCi0gKipBcmd1bWVudCoqOg0KDQogICAgLSBgeC5gIDogVGhlIGRhdGEgbWF0cmljZXMsIHRpYmJsZXMgYW5kIGRhdGEgZnJhbWVzLCBjb250YWluaW5nIHRoZSBkYXRhIHBvaW50cyB0byBiZSBhc3NpZ25lZCB0byBzb21lIGNsdXN0ZXIuDQogICAgLSBgY2VudHJvaWRzYCA6IFRoZSBtYXRyaXggb3IgZGF0YSBmcmFtZSBjb250YWluaW5nIHRoZSBjZW50cm9pZHMgKGJ5IHJvdykuDQogICAgLSBgZGl2YCA6IHRoZSB0eXBlIG9mIGRpdmVyZ2VuY2UgdG8gYmUgdXNlZC4NCiAgICAtIGBkZWdgIDogdGhlIGRlZ3JlZSBvZiBwb2x5bm9taWFsIEJEIChpZiBvbmUgaXMgdXNlZCkuDQogICAgDQotICoqVmFsdWUqKjogDQoNCiAgICBUaGUgZWFjaCBvZiB0aGUgdHdvIGZ1bmN0aW9ucyByZXR1cm5zIGFyZ3VtZW50cyBmb3Igb25lIGFub3RoZXI6DQogICAgDQogICAgLSBgZmluZENsb3Nlc3RDZW50cm9pZGAgcmV0dXJucyBhIHZlY3RvciBvZiBzaXplIGVxdWFscyB0byB0aGUgbnVtYmVyIG9mIHJvd3Mgb2YgZGF0YSBtYXRyaXggYHguYCwgY29udGFpbmluZyB0aGUgY2x1c3RlciBsYWJlbHMgb2YgdGhlIGRhdGEgcG9pbnRzLg0KICAgIC0gYG5ld0NlbnRyb2lkc2AgcmV0dXJucyB0aGUgbWF0cml4IG9mIGNlbnRyb2lkcy4NCg0KYGBge3J9DQpmaW5kQ2xvc2VzdENlbnRyb2lkIDwtIGZ1bmN0aW9uKHguLCBjZW50cm9pZHMuLCBkaXYsIGRlZyA9IDMpew0KICBkaXN0IDwtIEJyZWdtYW5EaXYoeC4sIGNlbnRyb2lkcy4sIGRpdiwgZGVnKQ0KICBjbHVzdCA8LSAxOm5yb3coeC4pICU+JQ0KICAgIG1hcF9pbnQoLmYgPSB+IHdoaWNoLm1pbihkaXN0Wy54LF0pKQ0KICByZXR1cm4oY2x1c3QpDQp9DQpuZXdDZW50cm9pZHMgPC0gZnVuY3Rpb24oeC4sIGNsdXN0ZXJzLil7DQogIGNlbnRyb2lkcyA8LSB1bmlxdWUoY2x1c3RlcnMuKSAlPiUNCiAgICBtYXBfZGZyKC5mID0gfiBjb2xNZWFucyh4LltjbHVzdGVycy4gPT0gLngsIF0pKQ0KICByZXR1cm4oY2VudHJvaWRzKQ0KfQ0KYGBgDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PkZ1bmN0aW9uPC91Pjwvc3Bhbj4gOiBga21lYW5zQkRgDQotLS0tDQoNClRoaXMgZnVuY3Rpb24gcGVyZm9ybXMgJEskLW1lYW5zIGFsZ29yaXRobSB3aXRoIEJEcy4gDQoNCi0gKipBcmd1bWVudCoqOg0KDQogICAgLSBgdHJhaW5faW5wdXRgIDogVGhlIGRhdGEgbWF0cmljZXMsIHRpYmJsZXMgYW5kIGRhdGEgZnJhbWVzLCBjb250YWluaW5nIHRoZSBkYXRhIHBvaW50cy4NCiAgICAtIGBLYCA6IFRoZSBudW1iZXIgb2YgY2x1c3RlcnMuDQogICAgLSBgbl9zdGFydGAgOiB0aGUgbnVtYmVyIG9mIHRpbWVzIHRvIHBlcmZvcm0gdGhlIGFsZ29yaXRobSwgYW5kIHRoZSBiZXN0IG9uZSBhbW9uZyB0aGVtIGlzIGNob3NlbiB0byBiZSB0aGUgZmluYWwgcmVzdWx0LiBUaGlzIGlzIGRvbmUgdG8gYXZvaWQgbG9jYWwgb3B0aW1hbCBzb2x1dGlvbnMuIEJ5IGRlZmF1bHQsIGBuX3N0YXJ0ID0gNWAuDQogICAgLSBgbWF4SXRlcmAgOiB0aGUgbWF4aW11bSBudW1iZXIgb2YgaXRlcmF0aW9ucyBpbiBjYXNlIHRoZSBhbGdvcml0aG0gZG9lcyBub3QgY29udmVyZ2UuIEJ5IGRlZmF1bHQsIGBtYXhJdGVyID0gNTAwYC4NCiAgICAtIGBkZWdgIDogdGhlIGRlZ3JlZSBvZiBwb2x5bm9taWFsIEJEIChpZiBvbmUgaXMgdXNlZCkuDQogICAgLSBgc2NhbGVfaW5wdXRgIDogbG9naWNhbCB2YWx1ZSBjb250cm9sbGluZyB3aGV0aGVyIHRvIHNjYWxlIHRoZSBpbnB1dCB0byBiZSBpbiAkKDAsMSkkIG9yIG5vdC4gQnkgZGVmYXVsdCwgYHNjYWxlX2lucHV0ID0gRkFMU0VgLg0KICAgIC0gYGRpdmAgOiB0aGUgdHlwZSBvZiBkaXZlcmdlbmNlIHRvIGJlIHVzZWQuIEJ5IGRlZmF1bHQsIGBkaXYgPSAiZXVjbGlkZWFuImAgYW5kIHRoZSB1c3VhbCAkSyQtbWVhbnMgYWxnb3JpdGhtIGlzIHBlcmZvcm1lZC4NCiAgICAtIGBzcGxpdHNgIDogcmVhbCBudW1iZXIgYmV0d2VlbiAkMCQgYW5kICQxJCBzcGVjaWZ5aW5nIHRoZSBwcm9wb3J0aW9uIG9mIHRyYWluaW5nIGRhdGEgdG8gYmUgdXNlZCB0byBwZXJmb3JtICRLJC1tZWFucyBhbGdvcml0aG0uIFRoZSByZW1haW5pbmcgcGFydCB3aXRoIGJlIHVzZWQgZm9yIHRoZSBhZ2dyZWdhdGlvbi4gQnkgZGVmYXVsdCwgYHNwbGl0cyA9IDFgIGFuZCBhbGwgdGhlIGlucHV0IGRhdGEgYXJlIHVzZWQuDQogICAgLSBgZXBzaWxvbmAgOiB0aGUgc3RvcHBpbmcgdGhyZXNob2xkIGNyaXRlcmlvbiB0byBzdG9wIHRoZSBhbGdvcml0aG0uIEJ5IGRlZmF1bHQsIGBlcHNpbG9uID0gMWUtMTBgLg0KICAgIC0gYGNlbnRlcl9gLCBgc2NhbGVfYCA6IHRoZSBjZW50ZXIgYW5kIHNjYWxlIHRvIGJlIHVzZWQgdG8gc2NhbGUgdGhlIGlucHV0IGRhdGEuIEJ5IGRlZmF1bHQsIHRoZXkgYXJlIGBOVUxMYC4NCiAgICAtIGBpZF9zaHVmZmxlYCA6IGEgbG9naWNhbCB2ZWN0b3Igc3BlY2lmeWluZyB3aGljaCBwYXJ0IG9mIHRoZSB0cmFpbmluZyBkYXRhIHdpbGwgYmUgc2VsZWN0ZWQgdG8gcGVyZm9ybSB0aGUgYWxnb3JpdGhtLiBUaGlzIGlzIGltcG9ydGFudCB3aGVuIHdlIHdhbnQgdG8gcGVyZm9ybSB0aGUgYWxnb3JpdGhtIG9uIHRoZSBzYW1lIHNldCBvZiBkYXRhIHBvaW50cyBidXQgd2l0aCBkaWZmZXJlbnQgQkRzLiANCiAgICANCi0gKipWYWx1ZSoqOiANCg0KICAgIFRoaXMgZnVuY3Rpb24gcmV0dXJucyBhIGxpc3Qgb2YgdGhlIGZvbGxvd2luZyBvYmplY3RzOg0KICAgIC0gYGNlbnRyb2lkc2AgOiB0aGUgbWF0cml4IG9mIHRoZSBvYnRhaW5lZCBjZW50cm9pZHMuDQogICAgLSBgY2x1c3RlcnNgIDogYSB2ZWN0b3Igb2YgY2x1c3RlciBsYWJlbCBvZiB0aGUgZGF0YSBwb2ludHMuDQogICAgLSBgdHJhaW5fZGF0YWAgOiBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgaW1wb3J0YW50IG9iamVjdHMNCiAgICAgICAgLSBgWF90cmFpbmAgOiB0aGUgdHJhaW5pbmcgZGF0YSB1c2VkIGZvciB0aGUgYWxnb3JpdGhtLg0KICAgICAgICAtIGBYX3JlbWFpbmAgOiB0aGUgcmVtYWluaW5nIHBhcnQgb2YgdGhlIGlucHV0IGRhdGEgdXNlZCBmb3IgdGhlIGFnZ3JlZ2F0aW9uLg0KICAgICAgICAtIGBpZF9yZW1haW5gIDogdGhlIGxvZ2ljYWwgdmVjdG9yIHNwZWNpZnlpbmcgdGhlIHJlbWFpbmluZyBwYXJ0IChgWF9yZW1haW5gKSBvZiB0aGUgaW5wdXQgZGF0YS4NCiAgICAtIGBwYXJhbWV0ZXJzYCA6IHRoZSBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0czoNCiAgICAgICAgLSBgZGl2YCA6IGRpdmVyZ2VuY2UgdXNlZC4NCiAgICAgICAgLSBgZGVnYCA6IHRoZSBkZWdyZWUgb2YgcG9seW5vbWlhbCBCRCAoaWYgb25lIGlzIHVzZWQpLg0KICAgICAgICAtIGBjZW50ZXJfYCwgYHNjYWxlX2AgOiB0aGUgY2VudGVyIGFuZCBzY2FsZSB1c2VkIHRvIHNjYWxlIHRoZSBpbnB1dCBkYXRhLg0KICAgIC0gYHJ1bm5pbmdfdGltZWA6IHRoZSBjb21wdXRhdGlvbmFsIHRpbWUgb2YgdGhlIGFsZ29yaXRobS4NCg0KYGBge3J9DQprbWVhbnNCRCA8LSBmdW5jdGlvbih0cmFpbl9pbnB1dCwNCiAgICAgICAgICAgICAgICAgICAgIEssDQogICAgICAgICAgICAgICAgICAgICBuX3N0YXJ0ID0gNSwNCiAgICAgICAgICAgICAgICAgICAgIG1heEl0ZXIgPSA1MDAsDQogICAgICAgICAgICAgICAgICAgICBkZWcgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgIGRpdiA9ICJldWNsaWRlYW4iLA0KICAgICAgICAgICAgICAgICAgICAgc3BsaXRzID0gMSwNCiAgICAgICAgICAgICAgICAgICAgIGVwc2lsb24gPSAxZS0xMCwNCiAgICAgICAgICAgICAgICAgICAgIGNlbnRlcl8gPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgc2NhbGVfID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgIGlkX3NodWZmbGUgPSBOVUxMKXsNCiAgc3RhcnRfdGltZSA8LSBTeXMudGltZSgpDQogICMgRGlzdG9ydGlvbiBmdW5jdGlvbg0KICBYIDwtIGFzLm1hdHJpeCh0cmFpbl9pbnB1dCkNCiAgTiA8LSBkaW0oWCkNCiAgaWYoc2NhbGVfaW5wdXQpew0KICAgIGlmKCEoaXMubnVsbChjZW50ZXJfKSAmIGlzLm51bGwoc2NhbGVfKSkpew0KICAgICAgaWYobGVuZ3RoKGNlbnRlcl8pID09IDEpew0KICAgICAgICBjZW50ZXJfIDwtIHJlcChjZW50ZXJfLCBOWzJdKQ0KICAgICAgfQ0KICAgICAgaWYobGVuZ3RoKHNjYWxlXykgPT0gMSl7DQogICAgICAgIHNjYWxlXyA8LSByZXAoc2NhbGVfLCBOWzJdKQ0KICAgICAgfQ0KICAgIH0gZWxzZXsNCiAgICAgIG1pbl8gPC0gYXBwbHkoWCwgMiwgRlVOID0gbWluKQ0KICAgICAgY18gPC0gYWJzKGNvbE1lYW5zKFgpLzUpDQogICAgICBjZW50ZXJfIDwtIG1pbl8gLSBjXw0KICAgICAgc2NhbGVfIDwtIGFwcGx5KFgsIDIsIEZVTiA9IG1heCkgLSBjZW50ZXJfICsgMQ0KICAgIH0NCiAgICBYIDwtIHNjYWxlKFgsIGNlbnRlciA9IGNlbnRlcl8sIHNjYWxlID0gc2NhbGVfKQ0KICB9DQogIGlmKGlzLm51bGwoaWRfc2h1ZmZsZSkpew0KICAgIHRyYWluX2lkIDwtIHJlcChUUlVFLCBOWzFdKQ0KICAgIGlmKHNwbGl0cyA8IDEpew0KICAgICAgdHJhaW5faWRbc2FtcGxlKE5bMV0sIGZsb29yKE5bMV0qKDEtc3BsaXRzKSkpXSA8LSBGQUxTRQ0KICAgIH0NCiAgfSBlbHNlew0KICAgIHRyYWluX2lkIDwtIGlkX3NodWZmbGUNCiAgfQ0KICBYX3RyYWluMSA8LSBYW3RyYWluX2lkLF0NCiAgWF90cmFpbjIgPC0gWFshdHJhaW5faWQsXQ0KICBtdSA8LSBhcy5tYXRyaXgoY29sTWVhbnMoWF90cmFpbjEpKQ0KICBkaXN0b3J0aW9uIDwtIGZ1bmN0aW9uKGNsdXMpew0KICAgIGNlbnQgPC0gbmV3Q2VudHJvaWRzKFhfdHJhaW4xLCBjbHVzKQ0KICAgIHZhcl93aXRoaW4gPC0gMTpLICU+JQ0KICAgICAgbWFwKC5mID0gfiBCcmVnbWFuRGl2KFhfdHJhaW4xW2NsdXMgPT0gLngsXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VudFsueCxdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXYsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlZykpICU+JQ0KICAgICAgbWFwKC5mID0gc3VtKSAlPiUNCiAgICAgIFJlZHVjZSgiKyIsIC4pDQogICAgcmV0dXJuKHZhcl93aXRoaW4pDQogIH0NCiAgIyBLbWVhbnMgYWxnb3JpdGhtDQogIGttZWFuc1dpdGhCRCA8LSBmdW5jdGlvbih4Liwgay4sIG1heGl0ZXIuLCBlcHMuKSB7DQogICAgbi4gPC0gbnJvdyh4LikNCiAgICAjIGluaXRpYWxpemF0aW9uDQogICAgaW5pdCA8LSBzYW1wbGUobi4sIGsuKQ0KICAgIGNlbnRyb2lkc19vbGQgPC0geC5baW5pdCxdDQogICAgaSA8LSAwDQogICAgd2hpbGUoaSA8IG1heEl0ZXIpew0KICAgICAgIyBBc3NpZ25tZW50IHN0ZXANCiAgICAgIGNsdXN0ZXJzIDwtIGZpbmRDbG9zZXN0Q2VudHJvaWQoeC4sIGNlbnRyb2lkc19vbGQsIGRpdiwgZGVnKQ0KICAgICAgIyBSZWNvbXB1dGUgY2VudHJvaWRzDQogICAgICBjZW50cm9pZHNfbmV3IDwtIG5ld0NlbnRyb2lkcyh4LiwgY2x1c3RlcnMpDQogICAgICBpZiAoKHN1bShpcy5uYShjZW50cm9pZHNfbmV3KSkgPiAwKSB8DQogICAgICAgICAgKG5yb3coY2VudHJvaWRzX25ldykgIT0gay4pKSB7DQogICAgICAgIGluaXQgPC0gc2FtcGxlKG4uLCBrLikNCiAgICAgICAgY2VudHJvaWRzX29sZCA8LSB4Lltpbml0LF0NCiAgICAgICAgd2FybmluZygiTkEgcHJvZHVjZWQgLT4gcmVpbml0aWFsaXplIGNlbnRyb2lkcy4uLiEiKQ0KICAgICAgfQ0KICAgICAgZWxzZXsNCiAgICAgICAgaWYoc3VtKGFicyhjZW50cm9pZHNfbmV3IC0gY2VudHJvaWRzX29sZCkpID4gZXBzLil7DQogICAgICAgICAgY2VudHJvaWRzX29sZCA8LSBjZW50cm9pZHNfbmV3DQogICAgICAgIH0gZWxzZXsNCiAgICAgICAgICBicmVhaw0KICAgICAgICB9DQogICAgICB9DQogICAgICBpIDwtIGkgKyAxDQogICAgfQ0KICAgIHJldHVybihjbHVzdGVycykNCiAgfQ0KICByZXN1bHRzIDwtIDE6bl9zdGFydCAlPiUgDQogICAgbWFwX2RmYyguZiA9IH4gdGliYmxlKCJ7ey54fX0iIDo9IGttZWFuc1dpdGhCRChYX3RyYWluMSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBLLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4SXRlciwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlcHNpbG9uKSkpDQogIG9wdF9pZCA8LSAxOm5fc3RhcnQgJT4lDQogICAgbWFwX2RibCguZiA9IH4gZGlzdG9ydGlvbihyZXN1bHRzW1sueF1dKSkgJT4lDQogICAgd2hpY2gubWluDQogIGNsdXN0ZXIgPC0gY2x1c3RlcnMgPC0gcmVzdWx0c1tbb3B0X2lkXV0NCiAgaiA8LSAxDQogIElEIDwtIHVuaXF1ZShjbHVzdGVyKQ0KICBmb3IgKGkgaW4gSUQpIHsNCiAgICBjbHVzdGVyc1tjbHVzdGVyID09IGldID0gag0KICAgIGogPSAgaiArIDENCiAgfQ0KICBjZW50cm9pZHMgPSBuZXdDZW50cm9pZHMoWF90cmFpbjEsIGNsdXN0ZXJzKQ0KICB0aW1lX3Rha2VuIDwtIFN5cy50aW1lKCkgLSBzdGFydF90aW1lDQogIHJldHVybigNCiAgICBsaXN0KA0KICAgICAgY2VudHJvaWRzID0gY2VudHJvaWRzLA0KICAgICAgY2x1c3RlcnMgPSBjbHVzdGVycywNCiAgICAgIHRyYWluX2RhdGEgPSBsaXN0KFhfdHJhaW4gPSBYX3RyYWluMSwNCiAgICAgICAgICAgICAgICAgICAgICAgIFhfcmVtYWluID0gWF90cmFpbjIsDQogICAgICAgICAgICAgICAgICAgICAgICBpZF9yZW1haW4gPSAhdHJhaW5faWQpLA0KICAgICAgcGFyYW1ldGVycyA9IGxpc3QoZGl2ID0gZGl2LA0KICAgICAgICAgICAgICAgICAgICAgICAgZGVnID0gZGVnLA0KICAgICAgICAgICAgICAgICAgICAgICAgY2VudGVyXyA9IGNlbnRlcl8sDQogICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV8gPSBzY2FsZV8pLA0KICAgICAgcnVubmluZ190aW1lID0gdGltZV90YWtlbg0KICAgICkNCiAgKQ0KfQ0KYGBgDQoNCi0tLS0NCg0KPiAqKkV4YW1wbGUuMSoqOiBXZSBwZXJmb3JtICRLJC1tZWFucyBhbGdvcml0aG0gd2l0aCBgImdrbCJgIEJEIG9uIFtBYmFsb25lXShodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvbWFjaGluZS1sZWFybmluZy1kYXRhYmFzZXMvYWJhbG9uZSkgZGF0YXNldC4NCg0KLS0tLQ0KDQpgYGB7cn0NCnBhY21hbjo6cF9sb2FkKHJlYWRyKQ0KY29sbmFtZSA8LSBjKCJUeXBlIiwgIkxvbmdlc3RTaGVsbCIsICJEaWFtZXRlciIsICJIZWlnaHQiLCAiV2hvbGVXZWlnaHQiLCAiU2h1Y2tlZFdlaWdodCIsICJWaXNjZXJhV2VpZ2h0IiwgIlNoZWxsV2VpZ2h0IiwgIlJpbmdzIikNCmRmIDwtIHJlYWRyOjpyZWFkX2RlbGltKCJodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvbWFjaGluZS1sZWFybmluZy1kYXRhYmFzZXMvYWJhbG9uZS9hYmFsb25lLmRhdGEiLCBjb2xfbmFtZXMgPSBjb2xuYW1lLCBkZWxpbSA9ICIsIiwgc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkNCm4gPC0gbnJvdyhkZikNCnRyYWluIDwtIGxvZ2ljYWwobikNCnRyYWluW3NhbXBsZShuLCAgZmxvb3IobiowLjgpKV0gPC0gVFJVRQ0KY2wgPC0gZGZbdHJhaW4sMjoobmNvbChkZiktMSldICU+JQ0KICBrbWVhbnNCRChLID0gMywgZGl2ID0gImdrbCIsIHNwbGl0cyA9IDAuNSwgc2NhbGVfaW5wdXQgPSBUUlVFKQ0KdGFibGUoY2wkY2x1c3RlcnMpDQpgYGANCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICMxRkFBRTM7Ij48dT5TdGVwICRGJDogRml0dGluZyBwcmVkaWN0aXZlIG1vZGVsczwvdT48L3NwYW4+DQo9PT0NCg0KVGhpcyBzZWN0aW9uIGJ1aWxkcyBnbG9iYWwgbW9kZWxzIGJ5IGZpdHRpbmcgbG9jYWwgbW9kZWwgb24gZWFjaCBnaXZlbiBjbHVzdGVyIG9mIHRoZSBvYnRhaW5lZCBwYXJ0aXRpb24uIFRoaXMgY29ycmVzcG9uZHMgdG8gdGhlIHN0ZXAgJEYkIG9mIHRoZSBwcm9jZWR1cmUuDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PkZ1bmN0aW9uPC91Pjwvc3Bhbj4gOiBgZml0TG9jYWxNb2RlbHNgDQotLS0tDQoNClRoaXMgZnVuY3Rpb24gZml0cyBsb2NhbCBtb2RlbHMgb24gYWxsIGNsdXN0ZXJzIG9mIHRoZSBvYnRhaW5lZCBwYXJ0aXRpb24uDQoNCi0gKipBcmd1bWVudCoqOg0KDQogICAgLSBga21lYW5zX0JEYCA6IEFuIG9iamVjdCBvYnRhaW5lZCBmcm9tIGBrbWVhbnNCRGAgZnVuY3Rpb24uDQogICAgLSBgdHJhaW5fcmVzcG9uc2VgIDogVGhlIHZlY3RvciBvZiByZXNwb25zZSB2YXJpYWJsZSBjb3JyZXNwb25kaW5nIHRvIHRoZSBmdWxsIGBpbnB1dF9kYXRhYCBnaXZlbiB0byBga21lYW5CRGAgZnVuY3Rpb24uDQogICAgLSBgbW9kZWxgIDogVHlwZSBvZiBsb2NhbCBtb2RlbCB0byBmaXQgb24gYWxsIHRoZSBjbHVzdGVycyBvZiB0aGUgZ2l2ZW4gcGFydGl0aW9uLiBJdCBzaG91bGQgYmUgZWl0aGVyIGEgbW9kZWwgb2JqZWN0IHdoaWNoIGlzIGNvbXBhY3RpYmxlDQogICAgLSBgZm9ybXVsYWAgOiBUaGUgZGVncmVlIG9mIHBvbHlub21pYWwgQkQgKGlmIG9uZSBpcyB1c2VkKS4NCg0KLSAqKlZhbHVlKio6DQoNCiAgICBUaGlzIGZ1bmN0aW9uIHJldHVybnMgYSBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0czoNCiAgICAtIGBsb2NhbF9tb2RlbHNgIDogYWxsIHRoZSBsb2NhbCBtb2RlbHMgZml0dGVkIG9uIGFsbCBjbHVzdGVycyBvZiB0aGUgZ2l2ZW4gcGFydGl0aW9uLg0KICAgIC0gYGttZWFuc19CRGAgOiB0aGUgYGttZWFuc0JEYCBvYmplY3QuDQogICAgLSBgZGF0YV9yZW1haW5gIDogYSBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0Og0KICAgICAgICAtIGBmaXRgIDogdGhlIGZpdHRlZCB2YWx1ZXMgb2YgdGhlIHJlbWFpbmluZyBwYXJ0IG9mIHRoZSBpbnB1dCBkYXRhLg0KICAgICAgICAtIGByZXNwb25zZWAgOiB0aGUgYWN0dWFsIHJlc3BvbnNlIHZhbHVlcyBjb3JyZXNwb25kaW5nIHRvIHRoZSByZW1haW5pbmcgaW5wdXQgZGF0YS4NCiAgICAtIGBydW5uaW5nX3RpbWVgIDogdGhlIGNvbXB1dGF0aW9uYWwgdGltZSBvZiB0aGUgYWxnb3JpdGhtLg0KDQpgYGB7cn0NCmZpdExvY2FsTW9kZWxzIDwtIGZ1bmN0aW9uKGttZWFuc19CRCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSAibG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZm9ybXVsYSA9IE5VTEwpew0KICBzdGFydF90aW1lIDwtIFN5cy50aW1lKCkNCiAgWF90cmFpbiA8LSBrbWVhbnNfQkQkdHJhaW5fZGF0YSRYX3RyYWluDQogIHlfdHJhaW4gPC0gdHJhaW5fcmVzcG9uc2VbIShrbWVhbnNfQkQkdHJhaW5fZGF0YSRpZF9yZW1haW4pXQ0KICBYX3JlbWFpbiA8LSBrbWVhbnNfQkQkdHJhaW5fZGF0YSRYX3JlbWFpbg0KICB5X3JlbWFpbiA8LSBOVUxMDQogIGlmKCFpcy5udWxsKFhfcmVtYWluKSl7DQogICAgeV9yZW1haW4gPC0gdHJhaW5fcmVzcG9uc2Vba21lYW5zX0JEJHRyYWluX2RhdGEkaWRfcmVtYWluXQ0KICB9DQogIHBhY21hbjo6cF9sb2FkKHRyZWUpDQogIHBhY21hbjo6cF9sb2FkKHJhbmRvbUZvcmVzdCkNCiAgbW9kZWxfIDwtIGlmZWxzZShtb2RlbCA9PSAidHJlZSIsIHRyZWU6OnRyZWUsIG1vZGVsKQ0KICBLIDwtIG5yb3coa21lYW5zX0JEJGNlbnRyb2lkcykNCiAgaWYgKGlzLm51bGwoZm9ybXVsYSkpew0KICAgIGZvcm0gPC0gZm9ybXVsYSh0YXJnZXQgfiAuKQ0KICB9DQogIGVsc2V7DQogICAgZm9ybSA8LSB1cGRhdGUoZm9ybXVsYSwgdGFyZ2V0IH4gLikNCiAgfQ0KICBkYXRhXyA8LSBiaW5kX2NvbHMoWF90cmFpbiwgInRhcmdldCI6PSB5X3RyYWluKQ0KICBmaXRfbG9va3VwIDwtIGxpc3QobG0gPSAiZml0dGVkLnZhbHVlcyIsDQogICAgICAgICAgICAgICAgICAgICByZiA9ICJwcmVkaWN0ZWQiKQ0KICBpZihpcy5jaGFyYWN0ZXIobW9kZWxfKSl7DQogICAgbW9kZWxfbG9va3VwIDwtIGxpc3QobG0gPSBsbSwNCiAgICAgICAgICAgICAgICAgICAgICAgICByZiA9IHJhbmRvbUZvcmVzdDo6cmFuZG9tRm9yZXN0KQ0KICAgIG1vZCA8LSBtYXAoLnggPSAxOkssIA0KICAgICAgICAgICAgICAgLmYgPSB+IG1vZGVsX2xvb2t1cFtbbW9kZWxfXV0oZm9ybXVsYSA9IGZvcm0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGFfW2ttZWFuc19CRCRjbHVzdGVycyA9PSAueCwgXSkpDQogIH0gZWxzZXsNCiAgICBtb2QgPC0gbWFwKC54ID0gMTpLLCANCiAgICAgICAgICAgICAgIC5mID0gfiBtb2RlbF8oZm9ybXVsYSA9IGZvcm0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0YV9ba21lYW5zX0JEJGNsdXN0ZXJzID09IC54LF0pKQ0KICB9DQogIHByZWQwIDwtIE5VTEwNCiAgaWYoIWlzLm51bGwoWF9yZW1haW4pKXsNCiAgICBwcmVkMCA8LSB2ZWN0b3IobW9kZSA9ICJudW1lcmljIiwgDQogICAgICAgICAgICAgICAgICAgIGxlbmd0aCA9IGxlbmd0aCh5X3JlbWFpbikpDQogICAgY2x1cyA8LSBmaW5kQ2xvc2VzdENlbnRyb2lkKHguID0gWF9yZW1haW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbnRyb2lkcy4gPSBrbWVhbnNfQkQkY2VudHJvaWRzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXYgPSBrbWVhbnNfQkQkcGFyYW1ldGVycyRkaXYsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlZyA9IGttZWFuc19CRCRwYXJhbWV0ZXJzJGRlZykNCiAgICBmb3IoaV8gaW4gMTpLKXsNCiAgICAgIHByZWQwW2NsdXMgPT0gaV9dIDwtIHByZWRpY3QobW9kW1tpX11dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lKFhfcmVtYWluW2NsdXMgPT0gaV8sXSkpDQogICAgfQ0KICB9DQogIHRpbWVfdGFrZW4gPC0gU3lzLnRpbWUoKSAtIHN0YXJ0X3RpbWUNCiAgcmV0dXJuKGxpc3QoDQogICAgbG9jYWxfbW9kZWxzID0gbW9kLA0KICAgIGttZWFuc19CRCA9IGttZWFuc19CRCwNCiAgICBkYXRhX3JlbWFpbiA9IGxpc3QoZml0ID0gcHJlZDAsDQogICAgICAgICAgICAgICAgICAgICAgIHJlc3BvbnNlID0geV9yZW1haW4pLA0KICAgIHJ1bm5pbmdfdGltZSA9IHRpbWVfdGFrZW4NCiAgKSkNCn0NCmBgYA0KDQotLS0tIA0KDQo+ICoqRXhhbXBsZS4yKio6IEZyb20gKipFeGFtcGxlLjEqKiBhYm92ZSwgbXVsdGlwbGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWxzIGFyZSBidWlsdCBvbiBhbGwgdGhlIG9idGFpbmVkIGNsdXN0ZXJzLiBUaGUgbWVhbiBzcXVhcmUgZXJyb3Igb2YgdGhpcyBtb2RlbCwgZXZhbHVhdGVkIG9uIHRoZSByZW1haW5pbmcgJDUwXCUkIG9mIHRoZSB0cmFpbmluZyBkYXRhIGlzIGNvbXB1dGVkLg0KDQotLS0tDQoNCmBgYHtyfQ0KZml0IDwtIGZpdExvY2FsTW9kZWxzKHRyYWluX3Jlc3BvbnNlID0gZGYkUmluZ3NbdHJhaW5dLA0KICAgICAgICAgICAgICAgICAgICAgIGttZWFuc19CRCA9IGNsLA0KICAgICAgICAgICAgICAgICAgICAgIG1vZGVsID0gImxtIikNCg0KbWVhbigoZml0JGRhdGFfcmVtYWluJHJlc3BvbnNlLSBmaXQkZGF0YV9yZW1haW4kZml0KV4yKQ0KYGBgDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PkZ1bmN0aW9uPC91Pjwvc3Bhbj4gOiBgbG9jYWxQcmVkaWN0YA0KLS0tLQ0KDQpUaGlzIGZ1bmN0aW9uIGFsbG93cyB1cyB0byBwcmVkaWN0IGFueSBuZXcgb2JzZXJ2YXRpb24gdXNpbmcgdGhlIGNhbmRpZGF0ZSBtb2RlbCAkXGNhbCBNX2o9KHtcY2FsIE19X3tqLGt9KV97az0xfV5NJCBjb3JyZXNwb25kaW5nIHRvIEJyZWdtYW4gZGl2ZXJnZW5jZSAke1xjYWwgQn1faiQsIGZvciBzb21lICRqXGluIEpcc3Vic2V0XHsxLC4uLixNXH0kLiANCg0KLSAqKkFyZ3VtZW50Kio6DQoNCiAgICAtIGBsb2NhbE1vZGVsc2AgOiBUaGUgbG9jYWwgbW9kZWwgb2JqZWN0IG9idGFpbmVkIGZyb20gYGZpdExvY2FsTW9kZWxzYCBmdW5jdGlvbi4NCiAgICAtIGBuZXdEYXRhYCA6IE5ldyBpbnB1dCBkYXRhIHRvIGJlIHByZWRpY3RlZCB1c2luZyB0aGUgY2FuZGlkYXRlIG1vZGVscyBnaXZlbiBpbiBgbG9jYWxNb2RlbHNgIGFyZ3VtZW50Lg0KICAgIA0KLSAqKlZhbHVlKio6DQoNCiAgICBUaGlzIGZ1bmN0aW9uIHJldHVybnMgYSBwcmVkaWN0ZWQgdmVjdG9yIG9mIHRoZSBgbmV3RGF0YWAuDQoNCmBgYHtyfQ0KbG9jYWxQcmVkaWN0IDwtIGZ1bmN0aW9uKGxvY2FsTW9kZWxzLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG5ld0RhdGEpew0KICBrbWVhbl9CRCA8LSBsb2NhbE1vZGVscyRrbWVhbnNfQkQNCiAgSyA8LSBucm93KGttZWFuX0JEJGNlbnRyb2lkcykNCiAgbmV3RGF0YV8gPC0gbmV3RGF0YQ0KICBpZighKGlzLm51bGwoa21lYW5fQkQkcGFyYW1ldGVycyRjZW50ZXJfKSkpew0KICAgIG5ld0RhdGFfIDwtIHNjYWxlKG5ld0RhdGEsDQogICAgICAgICAgICAgICAgICAgICAgY2VudGVyID0ga21lYW5fQkQkcGFyYW1ldGVycyRjZW50ZXJfLA0KICAgICAgICAgICAgICAgICAgICAgIHNjYWxlID0ga21lYW5fQkQkcGFyYW1ldGVycyRzY2FsZV8pDQogICAgaWQwIDwtIChuZXdEYXRhXyA8PSAwKQ0KICAgIGlmKHN1bShpZDApID4gMCl7DQogICAgICBtaW5fIDwtIG1pbihuZXdEYXRhX1tpZDBdKQ0KICAgICAgbmV3RGF0YV9baWQwXSA8LSBydW5pZihzdW0oaWQwKSwgbWluKDFlLTMsIG1pbl8vMTApLCBtaW5fKQ0KICAgIH0NCiAgfQ0KICBjbHVzIDwtIGZpbmRDbG9zZXN0Q2VudHJvaWQoeC4gPSBuZXdEYXRhXywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbnRyb2lkcy4gPSBrbWVhbl9CRCRjZW50cm9pZHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXYgPSBrbWVhbl9CRCRwYXJhbWV0ZXJzJGRpdiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlZyA9IGttZWFuX0JEJHBhcmFtZXRlcnMkZGVnKQ0KICBwcmVkMCA8LSB2ZWN0b3IobW9kZSA9ICJudW1lcmljIiwgbGVuZ3RoID0gbnJvdyhuZXdEYXRhXykpDQogIGZvcihpXyBpbiAxOkspew0KICAgIHByZWQwW2NsdXMgPT0gaV9dIDwtIHByZWRpY3QobG9jYWxNb2RlbHMkbG9jYWxfbW9kZWxzW1tpX11dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZShuZXdEYXRhX1tjbHVzID09IGlfLF0pKQ0KICB9DQogIHByZWQwIDwtIGFzX3RpYmJsZShwcmVkMCkNCiAgbmFtZXMocHJlZDApIDwtIGlmZWxzZShrbWVhbl9CRCRwYXJhbWV0ZXJzJGRpdiA9PSAicG9seW5vbWlhbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJwb2x5bm9taWFsIiwga21lYW5fQkQkcGFyYW1ldGVycyRkZWcpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGttZWFuX0JEJHBhcmFtZXRlcnMkZGl2KQ0KICByZXR1cm4ocHJlZDApDQp9DQpgYGANCg0KLS0tLQ0KDQo+ICoqRXhhbXBsZS4zKio6IFRoZSB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIGNhbmRpZGF0ZSBtb2RlbCBjb3JyZXNwb25kaW5nIHRvIGAiZ2tsImAgZGl2ZXJnZW5jZSBpcyBjb21wYXJlZCB0byByYW5kb20gZm9yZXN0IHJlZ3Jlc3Npb24gb24gYSAkMjBcJSQgdGVzdGluZyBkYXRhLg0KDQotLS0tDQoNCmBgYHtyfQ0KeV9oYXQgPC0gbG9jYWxQcmVkaWN0KGZpdCwNCiAgICAgICAgICAgICAgICAgICAgICBkZlshdHJhaW4sIDI6KG5jb2woZGYpLTEpLF0pDQpyZiA8LSByYW5kb21Gb3Jlc3QoUmluZ3MgfiAuLCBkYXRhID0gZGZbdHJhaW4sMjpuY29sKGRmKV0pDQptZWFuKChwcmVkaWN0KHJmLCBuZXdkYXRhID0gZGZbIXRyYWluLDI6bmNvbChkZildKS0gZGYkUmluZ3NbIXRyYWluXSleMikNCm1lYW4oKHlfaGF0JGdrbC1kZiRSaW5nc1shdHJhaW5dKV4yKQ0KYGBgDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+PHU+U3RlcCAkQyQ6IENvbWJpbmluZyBtZXRob2RzPC91Pjwvc3Bhbj4NCj09PQ0KDQpUaGUgYWdncmVnYXRpb24gbWV0aG9kcyBhcmUgYXZhaWxhYmxlIFtoZXJlIDxzcGFuIHN0eWxlPSJjb2xvcjogIzA5N0JDMSI+IGByIGZvbnRhd2Vzb21lOjpmYSgiZ2l0aHViIilgPC9zcGFuPl0oaHR0cHM6Ly9naXRodWIuY29tL2hhc3NvdGhlYS9BZ2dyZWdhdGlvbk1ldGhvZHMpLiBUaGUgYWdncmVnYXRpb24gbWV0aG9kcyBhcmUgaW1wb3J0ZWQgaW50byA8c3BhbiBzdHlsZT0iY29sb3I6ICMwMjg3RDg7Ij4gKipSc3R1ZGlvKiogPC9zcGFuPiBlbnZpcm9ubWVudC4NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQpwYWNtYW46OnBfbG9hZChkZXZ0b29scykNCiMjIyBLZXJuZWwgYmFzZWQgY29uc2Vuc3VhbCBhZ2dyZWdhdGlvbg0Kc291cmNlX3VybCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2hhc3NvdGhlYS9BZ2dyZWdhdGlvbk1ldGhvZHMvbWFpbi9LZXJuZWxBZ2dSZWcuUiIpDQojIyMgTWl4Q29icmENCnNvdXJjZV91cmwoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9oYXNzb3RoZWEvQWdncmVnYXRpb25NZXRob2RzL21haW4vTWl4Q29icmFSZWcuUiIpDQpgYGANCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+RnVuY3Rpb248L3U+PC9zcGFuPiA6IGBzdGVwS2AsIGBzdGVwRmAgYW5kIGBzdGVwQ2ANCi0tLS0NCg0KVGhlc2UgZnVuY3Rpb25zIGFsbG93IHRvIHNldCB0aGUgdmFsdWVzIG9mIHRoZSBwYXJhbWV0ZXJzIGluIHRoZSB0aHJlZSBzdGVwcyBvZiB0aGUgS0ZDIHByb2NlZHVyZS4gRWFjaCBmdW5jdGlvbiByZXR1cm5zIGEgbGlzdCBvZiBhbGwgdGhlIHBhcmFtZXRlcnMgZ2l2ZW4gaW4gaXRzIGFyZ3VtZW50cy4NCg0KYGBge3J9DQpzdGVwSyA9IGZ1bmN0aW9uKEssDQogICAgICAgICAgICAgICAgIG5fc3RhcnQgPSA1LA0KICAgICAgICAgICAgICAgICBtYXhJdGVyID0gMzAwLA0KICAgICAgICAgICAgICAgICBkZWcgPSAzLA0KICAgICAgICAgICAgICAgICBzY2FsZV9pbnB1dCA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICBkaXYgPSBOVUxMLA0KICAgICAgICAgICAgICAgICBzcGxpdHMgPSAwLjc1LA0KICAgICAgICAgICAgICAgICBlcHNpbG9uID0gMWUtMTAsDQogICAgICAgICAgICAgICAgIGNlbnRlcl8gPSBOVUxMLA0KICAgICAgICAgICAgICAgICBzY2FsZV8gPSBOVUxMKXsNCiAgcmV0dXJuKGxpc3QoSyA9IEssDQogICAgICAgICAgICAgIG5fc3RhcnQgPSBuX3N0YXJ0LA0KICAgICAgICAgICAgICBtYXhJdGVyID0gbWF4SXRlciwNCiAgICAgICAgICAgICAgZGVnID0gZGVnLA0KICAgICAgICAgICAgICBzY2FsZV9pbnB1dCA9IHNjYWxlX2lucHV0LA0KICAgICAgICAgICAgICBkaXYgPSBkaXYsDQogICAgICAgICAgICAgIHNwbGl0cyA9IHNwbGl0cywNCiAgICAgICAgICAgICAgZXBzaWxvbiA9IGVwc2lsb24sDQogICAgICAgICAgICAgIGNlbnRlcl8gPSBjZW50ZXJfICwNCiAgICAgICAgICAgICAgc2NhbGVfID0gc2NhbGVfKSkNCn0NCg0Kc3RlcEYgPSBmdW5jdGlvbihmb3JtdWxhID0gTlVMTCwgDQogICAgICAgICAgICAgICAgIG1vZGVsID0gImxtIil7DQogIHJldHVybihsaXN0KGZvcm11bGEgPSBmb3JtdWxhLCANCiAgICAgICAgICAgICAgbW9kZWwgPSBtb2RlbCkpDQp9DQoNCnN0ZXBDID0gZnVuY3Rpb24obl9jdiA9IDUsDQogICAgICAgICAgICAgICAgIG1ldGhvZCA9IGMoImNvYnJhIiwgIm1peGNvYnJhIiksDQogICAgICAgICAgICAgICAgIG9wdF9tZXRob2RzID0gYygiZ3JhZCIsICJncmlkIiksDQogICAgICAgICAgICAgICAgIGtlcm5lbHMgPSAiZ2F1c3NpYW4iLA0KICAgICAgICAgICAgICAgICBzY2FsZV9mZWF0dXJlcyA9IEZBTFNFKXsNCiAgcmV0dXJuKGxpc3Qobl9jdiA9IG5fY3YsDQogICAgICAgICAgICAgIG1ldGhvZCA9IG1ldGhvZCwNCiAgICAgICAgICAgICAgb3B0X21ldGhvZHMgPSBvcHRfbWV0aG9kcywNCiAgICAgICAgICAgICAga2VybmVscyA9IGtlcm5lbHMsDQogICAgICAgICAgICAgIHNjYWxlX2ZlYXR1cmVzID0gc2NhbGVfZmVhdHVyZXMpKQ0KfQ0KYGBgDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+PHU+RnVuY3Rpb248L3U+PC9zcGFuPjogYEtGQ3JlZ2ANCj09PQ0KDQpUaGlzIGZ1bmN0aW9uIGlzIHRoZSBjb21wbGV0ZSBpbXBsZW1lbnRhdGlvbiBvZiBLRkMgcHJvY2VkdXJlLg0KDQotICoqQXJndW1lbnQqKjoNCg0KICAgIC0gYHRyYWluX2lucHV0YCA6IFRoZSBtYXRyaXggb3IgZGF0YSBmcmFtZSBvZiB0cmFpbmluZyBpbnB1dCBkYXRhLg0KICAgIC0gYHRyYWluX3Jlc3BvbnNlYCA6IFRoZSB0cmFpbmluZyByZXNwb25zZSB2YXJpYWJsZS4NCiAgICAtIGB0ZXN0X2lucHV0YDogVGhlIHRlc3RpbmcgaW5wdXQgZGF0YS4NCiAgICAtIGB0ZXN0X3Jlc3BvbnNlYCA6IFRoZSByZXNwb25zZSB2YXJpYWJsZSBvZiB0aGUgdGVzdGluZyBkYXRhLiBJdCBpcyBvcHRpb25hbC4gSWYgaXQgaXMgbm90IGBOVUxMYCwgdGhlIG1lYW4gc3F1YXJlIGVycm9yIChtc2UpIGlzIGNvbXB1dGVkLg0KICAgIC0gYG5fY3ZgIDogVGhlIG51bWJlciBvZiBmb2xkcyBpbiBjcm9zcy12YWxpZGF0aW9uLg0KICAgIC0gYHBhcmFsbGVsYCA6IEEgbG9naWNhbCB2YWx1ZSBzcGVjaWZ5aW5nIHdoZXRoZXIgb3Igbm90IHRvIHBlcmZvcm0gdGhlIHN0ZXAgJEskIGluIHBhcmFsbGVsLiBCeSBkZWZhdWx0LCBgcGFyYWxsZWwgPSBUUlVFYCwgYW5kIG5vdGUgdGhhdCBpbnRlcm5ldCBjb25uZWN0aW9uIGlzIHJlcXVpcmVkIGluIHRoaXMgY2FzZS4NCiAgICAtIGBpbnZfc2lnbWFgLCBgYWxwYCA6IHRoZSBpbnZlcnNlIG5vcm1hbGl6ZWQgY29uc3RhbnQgJFxzaWdtYV57LTF9PjAkIGFuZCB0aGUgZXhwb25lbnQgJFxhbHBoYT4wJCBvZiBleHBvbmVudGlhbCBrZXJuZWw6ICRLKHgpPWVeey1cfHgvXHNpZ21hXHxee1xhbHBoYX19JCBmb3IgYW55ICR4XGluXG1hdGhiYntSfV5kJC4gQnkgZGVmYXVsdCwgYGludl9zaWdtYSA9IGAkXHNxcnR7MS8yfSQgYW5kIGBhbHBoYSA9IDJgIHdoaWNoIGNvcnJlc3BvbmRzIHRvIHRoZSBHYXVzc2lhbiBrZXJuZWwuDQogICAgLSBgS19zdGVwYCA6IFRoZSBvYmplY3Qgb2J0YWluZWQgZnJvbSBmdW5jdGlvbiBgc3RlcEtgIHdoaWNoIGFsbG93aW5nIHRvIHNldCB0aGUgdmFsdWVzIG9mIHRoZSBwYXJhbXRlcnMgaW4gc3RlcCAkSyQgb2YgdGhlIHByb2NlZHVyZS4NCiAgICAtIGBGX3N0ZXBgIDogVGhlIG9iamVjdCBvYnRhaW5lZCBmcm9tIGZ1bmN0aW9uIGBzdGVwRmAgd2hpY2ggYWxsb3dpbmcgdG8gc2V0IHRoZSB2YWx1ZXMgb2YgdGhlIHBhcmFtdGVycyBpbiBzdGVwICRGJCBvZiB0aGUgcHJvY2VkdXJlLg0KICAgIC0gYENfc3RlcGAgOiBUaGUgb2JqZWN0IG9idGFpbmVkIGZyb20gZnVuY3Rpb24gYHN0ZXBDYCB3aGljaCBhbGxvd2luZyB0byBzZXQgdGhlIHZhbHVlcyBvZiB0aGUgcGFyYW10ZXJzIGluIHN0ZXAgJEMkIG9mIHRoZSBwcm9jZWR1cmUuDQogICAgLSBgc2V0R3JhZFBhcmFtQWdnYCA6IFRoZSBvYmplY3QgZnJvbSB0aGUgYHNldEdyYWRQYXJhbWV0ZXJgIGZ1bmN0aW9uLCBhbGxvd2luZyB0byBzZXQgdGhlIHZhbHVlcyBvZiB0aGUgcGFyYW1ldGVycyBvZiAqKmdyYWRpZW50IGRlc2NlbnQqKiBhbGdvcml0aG0gZm9yIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6ICNFNjE4MEE7Ij4qKjFzdCBhZ2dyZWdhdGlvbiBtZXRob2QkXjEkKio8L3NwYW4+Lg0KICAgIC0gYHNldEdyaWRQYXJhbUFnZ2AgOiBUaGUgb2JqZWN0IGZyb20gdGhlIGBzZXRHcmlkUGFyYW1ldGVyYCBmdW5jdGlvbiwgYWxsb3dpbmcgdG8gc2V0IHRoZSB2YWx1ZXMgb2YgdGhlIHBhcmFtZXRlcnMgb2YgdGhlICoqZ3JpZCBzZWFyY2gqKiBhbGdvcml0aG0gZm9yIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6ICNFNjE4MEE7Ij4qKjFzdCBhZ2dyZWdhdGlvbiBtZXRob2QkXjEkKio8L3NwYW4+Lg0KICAgIC0gYHNldEdyYWRQYXJhbU1peGAgOiBUaGUgb2JqZWN0IGZyb20gdGhlIGBzZXRHcmFkUGFyYW1ldGVyX01peGAgZnVuY3Rpb24sIGFsbG93aW5nIHRvIHNldCB0aGUgdmFsdWVzIG9mIHRoZSBwYXJhbWV0ZXJzIG9mIHRoZSAqKmdyYWRpZW50IGRlc2NlbnQqKiBhbGdvcml0aG0gZm9yIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwODMyQ0QiPioqMm5kIGFnZ3JlZ2F0aW9uIG1ldGhvZCReMiQqKjwvc3Bhbj4uDQogICAgLSBgc2V0R3JpZFBhcmFtTWl4YCA6IFRoZSBvYmplY3QgZnJvbSB0aGUgYHNldEdyaWRQYXJhbWV0ZXJfTWl4YCBmdW5jdGlvbiwgYWxsb3dpbmcgdG8gc2V0IHRoZSB2YWx1ZXMgb2YgdGhlIHBhcmFtZXRlcnMgb2YgdGhlICoqZ3JpZCBzZWFyY2gqKiBhbGdvcml0aG0gZm9yIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwODMyQ0QiPioqMm5kIGFnZ3JlZ2F0aW9uIG1ldGhvZCReMiQqKjwvc3Bhbj4uDQogICAgDQotICoqVmFsdWUqKjoNCg0KICAgIFRoaXMgZnVuY3Rpb24gcmV0dXJucyBhIGxpc3Qgb2YgdGhlIGZvbGxvd2luZyBvYmplY3RzOg0KICAgIA0KICAgIC0gYHByZWRpY3RfZmluYWxgIDogdGhlIGZpbmFsIHByZWRpY3Rpb25zIGdpdmVuIGJ5IHRoZSBhZ2dyZWdhdGlvbiBvZiBhbGwgdGhlIGNhbmRpZGF0ZSBtb2RlbHMuDQogICAgLSBgcHJlZGljdF9sb2NhbGAgOiB0aGUgcHJlZGljdGlvbnMgZ2l2ZW4gYnkgYWxsIHRoZSBpbmRpdmlkdWFsIGNhbmRpZGF0ZSBtb2RlbHMuDQogICAgLSBgYWdnX21ldGhvZGAgOiB0aGUgbGlzdCBvZiBhZ2dyZWdhdGlvbiBtZXRob2RzIG9idGFpbmVkIGluIHRoZSBzdGVwICRDJCBvZiB0aGUgcHJvY2VkdXJlLg0KICAgIC0gYHJ1bm5pbmdfdGltZWAgOiB0aGUgY29tcHV0YXRpb25hbCB0aW1lIG9mIHRoZSBhbGdvcml0aG0uDQoNCi0tLS0NCg0KICA+ICoqUmVtYXJrLjIqKjogVGhlIGBwYXJhbGxlbGAgYXJndW1lbnQgYWJvdmUgcmVxdWlyZXMgaW50ZXJuZXQgY29ubmVjdGlvbiB0byBsb2FkIHRoZSBzb3VyY2UgY29kZXMgb2YgJEskLW1lYW5zIGFsZ29yaXRobSB3aXRoIEJEcyBmcm9tIFtHaXRIdWIgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDk3QkMxIj4gYHIgZm9udGF3ZXNvbWU6OmZhKCJnaXRodWIiKWA8L3NwYW4+XShodHRwczovL2dpdGh1Yi5jb20vaGFzc290aGVhL0tGQy1Qcm9jZWR1cmUvYmxvYi9tYXN0ZXIva21lYW5CRC5SKS4gSXQgaXMgcGVyZm9ybWVkIG9uIHRoZSBtYXhpbXVtIG51bWJlciBvZiBjbHVzdGVycyBvZiB5b3VyIG1hY2hpbmUsIGFuZCB0aGUgc3BlZWQgaXMgYXQgbGVhc3QgdHdvIHRpbWVzIGZhc3RlciB0aGFuIHdpdGhvdXQgcGFyYWxsZWxpc20sIGhvd2V2ZXIsIGl0IGlzIG5vdCBzbyBzdGFibGUgZGVwZW5kaW5nIG9uIHlvdXIgaW50ZXJuZXQgY29ubmVjdGlvbiBvciBtYWNoaW5lLiBBYm91dCB0aGUgYWdncmVnYXRpb24gbWV0aG9kcyBvZiBzdGVwICRDJCwNCg0KDQotIDxzcGFuIHN0eWxlPSJjb2xvcjogI0U2MTgwQTsiPiReMSQ8L3NwYW4+IGlzIHRoZSBrZXJuZWwtYmFzZWQgY29uc2Vuc3VhbCBhZ2dyZWdhdGlvbiBmb3IgcmVncmVzc2lvbiBieSBbSGFzICgyMDIxKV0oaHR0cHM6Ly9oYWwuYXJjaGl2ZXMtb3V2ZXJ0ZXMuZnIvaGFsLTAyODg0MzMzdjUpLiBUaGUgc291cmNlIGNvZGVzIG9mIHRoZXNlIGZ1bmN0aW9ucyBhcmUgYXZhaWxhYmxlIGluIFtLZXJuZWxBZ2dSZWcuUl0oaHR0cHM6Ly9naXRodWIuY29tL2hhc3NvdGhlYS9BZ2dyZWdhdGlvbk1ldGhvZHMvYmxvYi9tYWluL0tlcm5lbEFnZ1JlZy5SKSwgYW5kIHRoZSBkb2N1bWVudGF0aW9uIGlzIGF2YWlsYWJsZSBbaGVyZV0oaHR0cHM6Ly9oYXNzb3RoZWEuZ2l0aHViLmlvL2ZpbGVzL0tlcm5lbEFnZ1JlZy9LZXJuZWxBZ2dSZWcuaHRtbCkuDQotIDxzcGFuIHN0eWxlPSJjb2xvcjogIzA4MzJDRCI+JF4yJDwvc3Bhbj4gaXMgdGhlIGFnZ3JlZ2F0aW9uIHVzaW5nIGlucHV0LW91dHB1dCB0cmFkZS1vZmYgYnkgW0Zpc2NoZXIgYW5kIE1vdWdlb3QgKDIwMTkpXShodHRwczovL3d3dy5zY2llbmNlZGlyZWN0LmNvbS9zY2llbmNlL2FydGljbGUvcGlpL1MwMzc4Mzc1ODE4MzAyMzQ5KS4gVGhlIHNvdXJjZSBjb2RlcyBvZiB0aGVzZSBmdW5jdGlvbnMgYXJlIGF2YWlsYWJsZSBpbiBbTWl4Q29icmEuUl0oaHR0cHM6Ly9naXRodWIuY29tL2hhc3NvdGhlYS9BZ2dyZWdhdGlvbk1ldGhvZHMvYmxvYi9tYWluL01peENvYnJhUmVnLlIpLCBhbmQgdGhlIGRvY3VtZW50YXRpb24gaXMgYXZhaWxhYmxlIG9uIFtoZXJlXShodHRwczovL2hhc3NvdGhlYS5naXRodWIuaW8vZmlsZXMvS2VybmVsQWdnUmVnL01peENvYnJhUmVnLmh0bWwpLg0KDQotLS0tDQoNCg0KYGBge3J9DQpLRkNyZWcgPSBmdW5jdGlvbih0cmFpbl9pbnB1dCwNCiAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlLA0KICAgICAgICAgICAgICAgICAgdGVzdF9pbnB1dCwNCiAgICAgICAgICAgICAgICAgIHRlc3RfcmVzcG9uc2UgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgbl9jdiA9IDUsDQogICAgICAgICAgICAgICAgICBwYXJhbGxlbCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICBpbnZfc2lnbWEgPSBzcXJ0KC41KSwNCiAgICAgICAgICAgICAgICAgIGFscCA9IDIsDQogICAgICAgICAgICAgICAgICBLX3N0ZXAgPSBzdGVwSyhzcGxpdHMgPSAuNSksDQogICAgICAgICAgICAgICAgICBGX3N0ZXAgPSBzdGVwRigpLA0KICAgICAgICAgICAgICAgICAgQ19zdGVwID0gc3RlcEMoKSwNCiAgICAgICAgICAgICAgICAgIHNldEdyYWRQYXJhbUFnZyA9IHNldEdyYWRQYXJhbWV0ZXIoKSwNCiAgICAgICAgICAgICAgICAgIHNldEdyaWRQYXJhbUFnZyA9IHNldEdyaWRQYXJhbWV0ZXIoKSwNCiAgICAgICAgICAgICAgICAgIHNldEdyYWRQYXJhbU1peCA9IHNldEdyYWRQYXJhbWV0ZXJfTWl4KCksDQogICAgICAgICAgICAgICAgICBzZXRHcmlkUGFyYW1NaXggPSBzZXRHcmlkUGFyYW1ldGVyX01peCgpKXsNCiAgc3RhcnRfdGltZSA8LSBTeXMudGltZSgpDQogIGxvb2t1cF9kaXZfbmFtZXMgPC0gYygiZXVjbGlkZWFuIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAiZ2tsIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAibG9naXN0aWMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJpdGFrdXJhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAicG9seW5vbWlhbCIpDQogIGRpdl8gPC0gS19zdGVwJGRpdg0KICAjIyMgSyBzdGVwOiBLbWVhbnMgY2x1c3RlcmluZyB3aXRoIEJEcw0KICBpZiAoaXMubnVsbChLX3N0ZXAkZGl2KSl7DQogICAgZGl2ZXJnZW5jZXMgPC0gbG9va3VwX2Rpdl9uYW1lcw0KICAgIHdhcm5pbmcoIk5vIGRpdmVyZ2VuY2UgcHJvdmlkZWQhIEFsbCBvZiB0aGVtIGFyZSB1c2VkISIpDQogIH0NCiAgZWxzZXsNCiAgICBkaXZlcmdlbmNlcyA8LSBLX3N0ZXAkZGl2ICU+JSANCiAgICAgIG1hcF9jaHIoLmYgPSB+IG1hdGNoLmFyZyhhcmcgPSAueCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2hvaWNlcyA9IGxvb2t1cF9kaXZfbmFtZXMpKQ0KICB9DQogIGRpdl9saXN0IDwtIGRpdmVyZ2VuY2VzICU+JSANCiAgICBtYXAoLmYgPSAoXCh4KSBpZih4ICE9ICJwb2x5bm9taWFsIikgcmV0dXJuKHgpIGVsc2UgcmV0dXJuKHJlcCgicG9seW5vbWlhbCIsIGxlbmd0aChLX3N0ZXAkZGVnKSkpKSkgJT4lDQogICAgdW5saXN0DQogIGRlZ19saXN0IDwtIHJlcChOQSwgbGVuZ3RoKGRpdl8pKQ0KICBkZWdfbGlzdFtkaXZfbGlzdCA9PSAicG9seW5vbWlhbCJdIDwtIEtfc3RlcCRkZWcNCiAgZGl2X25hbWVzIDwtIG1hcDJfY2hyKC54ID0gZGl2X2xpc3QsDQogICAgICAgICAgICAgICAgICAgICAgICAueSA9IGRlZ19saXN0LA0KICAgICAgICAgICAgICAgICAgICAgICAgLmYgPSAoXCh4LCB5KSBpZihpcy5uYSh5KSkgcmV0dXJuKHgpIGVsc2UgcmV0dXJuKHBhc3RlMCh4LHkpKSkpDQogICMjIyBTdGVwIEs6IEttZWFucyBjbHVzdGVyaW5nIHdpdGggQnJlZ21hbiBkaXZlcmdlbmNlcw0KICBkbSA8LSBkaW0odHJhaW5faW5wdXQpDQogIGlkX3NodWZmbGUgPC0gdmVjdG9yKGxlbmd0aCA9IGRtWzFdKQ0KICBuX3RyYWluIDwtIGZsb29yKEtfc3RlcCRzcGxpdHMgKiBkbVsxXSkNCiAgaWRfc2h1ZmZsZVtzYW1wbGUoZG1bMV0sIG5fdHJhaW4pXSA8LSBUUlVFDQogIGlmKHBhcmFsbGVsKXsNCiAgICBudW1Db3JlcyA8LSBwYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKQ0KICAgIGRvUGFyYWxsZWw6OnJlZ2lzdGVyRG9QYXJhbGxlbChudW1Db3JlcykgIyB1c2UgbXVsdGljb3JlLCBzZXQgdG8gdGhlIG51bWJlciBvZiBvdXIgY29yZXMNCiAgICBrbWVhbl8gPC0gZm9yZWFjaChpPTE6bGVuZ3RoKGRpdl9uYW1lcykpICVkb3BhciUgew0KICAgICAgZGV2dG9vbHM6OnNvdXJjZV91cmwoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9oYXNzb3RoZWEvS0ZDLVByb2NlZHVyZS9tYXN0ZXIva21lYW5CRC5SIikNCiAgICAgIGttZWFuc0JEKHRyYWluX2lucHV0ID0gdHJhaW5faW5wdXQsDQogICAgICAgICAgICAgICBLID0gS19zdGVwJEssDQogICAgICAgICAgICAgICBkaXYgPSBkaXZfbGlzdFtpXSwNCiAgICAgICAgICAgICAgIG5fc3RhcnQgPSBLX3N0ZXAkbl9zdGFydCwNCiAgICAgICAgICAgICAgIG1heEl0ZXIgPSBLX3N0ZXAkbWF4SXRlciwNCiAgICAgICAgICAgICAgIGRlZyA9IGRlZ19saXN0W2ldLA0KICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBLX3N0ZXAkc2NhbGVfaW5wdXQsDQogICAgICAgICAgICAgICBzcGxpdHMgPSBLX3N0ZXAkc3BsaXRzLA0KICAgICAgICAgICAgICAgZXBzaWxvbiA9IEtfc3RlcCRlcHNpbG9uLA0KICAgICAgICAgICAgICAgY2VudGVyXyA9IEtfc3RlcCRjZW50ZXJfLA0KICAgICAgICAgICAgICAgc2NhbGVfID0gS19zdGVwJHNjYWxlXywNCiAgICAgICAgICAgICAgIGlkX3NodWZmbGUgPSBpZF9zaHVmZmxlKQ0KICAgIH0NCiAgICBkb1BhcmFsbGVsOjpzdG9wSW1wbGljaXRDbHVzdGVyKCkNCiAgfSBlbHNlew0KICAgIGttZWFuXyA8LSBtYXAyKC54ID0gZGl2X2xpc3QsDQogICAgICAgICAgICAgICAgICAgLnkgPSBkZWdfbGlzdCwNCiAgICAgICAgICAgICAgICAgICAuZiA9IH4ga21lYW5zQkQodHJhaW5faW5wdXQgPSB0cmFpbl9pbnB1dCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSyA9IEtfc3RlcCRLLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXYgPSAueCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9zdGFydCA9IEtfc3RlcCRuX3N0YXJ0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhJdGVyID0gS19zdGVwJG1heEl0ZXIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlZyA9IC55LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV9pbnB1dCA9IEtfc3RlcCRzY2FsZV9pbnB1dCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRzID0gS19zdGVwJHNwbGl0cywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXBzaWxvbiA9IEtfc3RlcCRlcHNpbG9uLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZW50ZXJfID0gS19zdGVwJGNlbnRlcl8sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlXyA9IEtfc3RlcCRzY2FsZV8sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkX3NodWZmbGUgPSBpZF9zaHVmZmxlKSkNCiAgfQ0KICBuYW1lcyhrbWVhbl8pIDwtIGRpdl9uYW1lcw0KICAjIyMgRiBzdGVwOiBGaXR0aW5nIHRoZSBjb3JyZXNwb25kaW5nIG1vZGVsIG9uIGVhY2ggb2JzZXJ2ZWQgY2x1c3Rlcg0KICBtb2RlbF8gPC0gZGl2X25hbWVzICU+JQ0KICAgIG1hcCguZiA9IH4gZml0TG9jYWxNb2RlbHMoa21lYW5fW1sueF1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcmVzcG9uc2UgPSB0cmFpbl9yZXNwb25zZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsID0gRl9zdGVwJG1vZGVsLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZm9ybXVsYSA9IEZfc3RlcCRmb3JtdWxhKSkNCiAgbmFtZXMobW9kZWxfKSA8LSBkaXZfbmFtZXMNCiAgcHJlZF9jb21iaW5lIDwtIG1vZGVsXyAlPiUNCiAgICBtYXBfZGZjKC5mID0gfiAueCRkYXRhX3JlbWFpbiRmaXQpDQogIHlfcmVtYWluIDwtIHRyYWluX3Jlc3BvbnNlWyFpZF9zaHVmZmxlXQ0KICBwcmVkX3Rlc3QgPC0gZGl2X25hbWVzICU+JQ0KICAgIG1hcF9kZmMoLmYgPSB+IGxvY2FsUHJlZGljdChtb2RlbF9bWy54XV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3RfaW5wdXQpKQ0KICBuYW1lcyhwcmVkX3Rlc3QpIDwtIG5hbWVzKHByZWRfY29tYmluZSkgPC0gZGl2X25hbWVzDQogICMgQyBzdGVwOiBDb25zZW5zdWFsIHJlZ3Jlc3Npb24gYWdncmVnYXRpb24gbWV0aG9kIHdpdGgga2VybmVsLWJhc2VkIENPQlJBDQogIGxpc3RfbWV0aG9kX2FnZyA8LSBsaXN0KG1peGNvYnJhID0gZnVuY3Rpb24ocHJlZCl7TWl4Q29icmFSZWcodHJhaW5faW5wdXQgPSB0cmFpbl9pbnB1dFshaWRfc2h1ZmZsZSxdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlID0geV9yZW1haW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdF9pbnB1dCA9IHRlc3RfaW5wdXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcHJlZGljdGlvbnMgPSBwcmVkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3RfcHJlZGljdGlvbnMgPSBwcmVkX3Rlc3QsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdF9yZXNwb25zZSA9IHRlc3RfcmVzcG9uc2UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBLX3N0ZXAkc2NhbGVfaW5wdXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfbWFjaGluZSA9IENfc3RlcCRzY2FsZV9mZWF0dXJlcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2N2ID0gQ19zdGVwJG5fY3YsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW52X3NpZ21hID0gaW52X3NpZ21hLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscCA9IGFscCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXJuZWxzID0gQ19zdGVwJGtlcm5lbHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3B0aW1pemVNZXRob2QgPSBDX3N0ZXAkb3B0X21ldGhvZHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0R3JhZFBhcmFtID0gc2V0R3JhZFBhcmFtTWl4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldEdyaWRQYXJhbSA9IHNldEdyaWRQYXJhbU1peCl9LA0KICAgICAgICAgICAgICAgICAgICAgICAgICBjb2JyYSA9IGZ1bmN0aW9uKHByZWQpe2tlcm5lbEFnZ1JlZyh0cmFpbl9kZXNpZ24gPSBwcmVkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbl9yZXNwb25zZSA9IHlfcmVtYWluLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0X2Rlc2lnbiA9IHByZWRfdGVzdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdF9yZXNwb25zZSA9IHRlc3RfcmVzcG9uc2UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX2lucHV0ID0gS19zdGVwJHNjYWxlX2lucHV0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV9tYWNoaW5lID0gQ19zdGVwJHNjYWxlX2ZlYXR1cmVzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBidWlsZF9tYWNoaW5lID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hY2hpbmVzID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9jdiA9IENfc3RlcCRuX2N2LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbnZfc2lnbWEgPSBzcXJ0KC41KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwID0gMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2VybmVscyA9IENfc3RlcCRrZXJuZWxzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcHRpbWl6ZU1ldGhvZCA9IENfc3RlcCRvcHRfbWV0aG9kcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0R3JhZFBhcmFtID0gc2V0R3JhZFBhcmFtQWdnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRHcmlkUGFyYW0gPSBzZXRHcmlkUGFyYW1BZ2cpfSkNCiAgcmVzIDwtIG1hcCgueCA9IENfc3RlcCRtZXRob2QsDQogICAgICAgICAgICAgLmYgPSB+IGxpc3RfbWV0aG9kX2FnZ1tbLnhdXShwcmVkX2NvbWJpbmUpKQ0KICBsaXN0X2FnZ19tZXRob2RzIDwtIGxpc3QoY29icmEgPSAiY29iIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1peGNvYnJhID0gIm1peCIpDQogIG5hbWVzKHJlcykgPC0gQ19zdGVwJG1ldGhvZA0KICBleHRfZnVuIDwtIGZ1bmN0aW9uKEwsIG5hbSl7DQogICAgdGFiIDwtIEwkZml0dGVkX2FnZ3JlZ2F0ZQ0KICAgIG5hbWVzKHRhYikgPC0gcGFzdGUwKG5hbWVzKHRhYiksICJfIiwgbmFtKQ0KICAgIHJldHVybih0YWIpDQogIH0NCiAgcHJlZF9maW4gPC0gQ19zdGVwJG1ldGhvZCAlPiUNCiAgICBtYXBfZGZjKC5mID0gfiBleHRfZnVuKHJlc1tbLnhdXSwgbGlzdF9hZ2dfbWV0aG9kc1tbLnhdXSkpDQogIHRpbWUudGFrZW4gPC0gU3lzLnRpbWUoKSAtIHN0YXJ0X3RpbWUNCiAgIyMjIFRvIGZpbmlzaA0KICBpZihpcy5udWxsKHRlc3RfcmVzcG9uc2UpKXsNCiAgICByZXR1cm4obGlzdCgNCiAgICBwcmVkaWN0X2ZpbmFsID0gcHJlZF9maW4sDQogICAgcHJlZGljdF9sb2NhbCA9IHByZWRfdGVzdCwNCiAgICBhZ2dfbWV0aG9kID0gcmVzLA0KICAgIHJ1bm5pbmdfdGltZSA9IHRpbWUudGFrZW4NCiAgKSkNCiAgfSBlbHNlew0KICAgIGVycm9yIDwtIGNiaW5kKHByZWRfdGVzdCwgcHJlZF9maW4pICU+JQ0KICAgICAgZHBseXI6Om11dGF0ZSh5X3Rlc3QgPSB0ZXN0X3Jlc3BvbnNlKSAlPiUNCiAgICAgIGRwbHlyOjpzdW1tYXJpc2VfYWxsKC5mdW5zID0gfiAoLiAtIHlfdGVzdCkpICU+JQ0KICAgICAgZHBseXI6OnNlbGVjdCgteV90ZXN0KSAlPiUNCiAgICAgIGRwbHlyOjpzdW1tYXJpc2VfYWxsKC5mdW5zID0gfiBtZWFuKC5eMikpDQogICAgcmV0dXJuKGxpc3QoDQogICAgICBwcmVkaWN0X2ZpbmFsID0gcHJlZF9maW4sDQogICAgICBwcmVkaWN0X2xvY2FsID0gcHJlZF90ZXN0LA0KICAgICAgYWdnX21ldGhvZCA9IHJlcywNCiAgICAgIG1zZSA9IGVycm9yLA0KICAgICAgcnVubmluZ190aW1lID0gdGltZS50YWtlbg0KICApKQ0KICB9DQp9DQpgYGANCg0KPiAqKkV4YW1wbGUuNCoqOiBBIGNvbXBsZXRlIEtGQyBwcm9jZWR1cmUgaXMgaW1wbGVtZW50ZWQgb24gdGhlIHNhbWUgQWJhbG9uZSBkYXRhLCB1c2luZyAkNiQgQkRzIGAiZXVjbGlkZWFuImAsIGAiaXRha3VyYSJgLCBgImdrbCJgLCBgImxvZ2lzdGljImAgYW5kIGAicG9seW5vbWlhbCJgIChvZiBkZWdyZWUgJDMkIGFuZCAkNiQpLiBCb3RoIGFnZ3JlZ2F0aW9uIG1ldGhvZHMgYXJlIHVzZWQgaW4gdGhlIHN0ZXAgJEMkLiBUd28ga2VybmVsIGZ1bmN0aW9ucyBhcmUgdXNlZCBmb3IgZWFjaCBhZ2dyZWdhdGlvbiBtZXRob2Q6IGAiZ2F1c3NpYW4iYCAod2l0aCBncmFkaWVudCBkZXNjZW50IGFsZ29yaXRobSkgYW5kIGAiZXBhbmVjaG5pa292ImAgKHdpdGggZ3JpZCBzZWFyY2ggYWxnb3JpdGhtKS4NCg0KYGBge3J9DQp0cmFpbjEgPC0gbG9naWNhbChuKQ0KdHJhaW4xW3NhbXBsZShuLCAgZmxvb3IobiowLjgpKV0gPC0gVFJVRQ0Ka2ZjMSA8LSBLRkNyZWcodHJhaW5faW5wdXQgPSBkZlt0cmFpbjEsMjpuY29sKGRmKV0sDQogICAgICAgICAgICAgICAgdHJhaW5fcmVzcG9uc2UgPSBkZiRSaW5nc1t0cmFpbjFdLA0KICAgICAgICAgICAgICAgIHRlc3RfaW5wdXQgPSBkZlshdHJhaW4xLDI6bmNvbChkZildLA0KICAgICAgICAgICAgICAgIEtfc3RlcCA9IHN0ZXBLKEsgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX2lucHV0ID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXYgPSBjKCJldWNsIiwgIml0YSIsICJna2wiLCAibG9nIiAsInBvbHkiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWcgPSBjKDMsIDYpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0cyA9IC41KSwNCiAgICAgICAgICAgICAgICBDX3N0ZXAgPSBzdGVwQyhtZXRob2QgPSBjKCJjb2JyYSIsICJtaXhjb2JyYSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9wdF9tZXRob2RzID0gYygiZ3JhZCIsICJncmlkIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2VybmVscyA9IGMoImdhdXNzaWFuIiwgImdhdXNzaWFuIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfZmVhdHVyZXMgPSBGQUxTRSksDQogICAgICAgICAgICAgICAgc2V0R3JhZFBhcmFtQWdnID0gc2V0R3JhZFBhcmFtZXRlcihyYXRlID0gMSksDQogICAgICAgICAgICAgICAgc2V0R3JpZFBhcmFtQWdnID0gc2V0R3JpZFBhcmFtZXRlcihtaW5fdmFsID0gLjAwMDAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X3ZhbCA9IDEwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl92YWwgPSAxMDApLA0KICAgICAgICAgICAgICAgIHNldEdyYWRQYXJhbU1peCA9IHNldEdyYWRQYXJhbWV0ZXJfTWl4KHJhdGUgPSBjKDEsMSkpLA0KICAgICAgICAgICAgICAgIHNldEdyaWRQYXJhbU1peCA9IHNldEdyaWRQYXJhbWV0ZXJfTWl4KG1pbl9hbHBoYSA9IDFlLTEwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9hbHBoYSA9IDUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluX2JldGEgPSAxZS0xMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfYmV0YSA9IDEwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fYWxwaGEgPSAyMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2JldGEgPSAyMCkpDQpgYGANCg0KPiBUaGUgbWVhbiBzcXVhcmUgZXJyb3JzIGV2YWx1YXRlZCBvbiAkMjBcJSQtdGVzdGluZyBkYXRhIG9mIHRoZSBhYm92ZSBjb21wdXRhdGlvbiBhcmUgcmVwb3J0ZWQgYmVsb3cuDQoNCmBgYHtyfQ0KcmYxIDwtIHJhbmRvbUZvcmVzdDo6cmFuZG9tRm9yZXN0KFJpbmdzIH4gLiwgZGF0YSA9IGRmW3RyYWluMSwyOm5jb2woZGYpXSkNCmtmYzEkcHJlZGljdF9maW5hbCAlPiUNCiAgbXV0YXRlKHJmID0gcHJlZGljdChyZjEsIG5ld2RhdGEgPSBkZlshdHJhaW4xLDI6bmNvbChkZildKSkgJT4lDQogIHN3ZWVwKE1BUkdJTiA9IDEsIFNUQVRTID0gZGYkUmluZ3NbIXRyYWluMV0sIEZVTiA9ICItIikgJT4lDQogIC5eMiAgJT4lDQogIGNvbE1lYW5zDQpgYGANCg0KLS0tDQoNCj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+JiMxMjgyMTQ7IFJlYWQgYWxzbyBbS2VybmVsQWdnUmVnXShcZmlsZXNcS2VybmVsQWdnUmVnLmh0bWwpIGFuZCBbTWl4Q29icmFSZWddKFxmaWxlc1xNaXhDb2JyYVJlZy5odG1sKTwvc3Bhbj4uDQoNCi0tLQ0KDQotIFtIYXMgZXQgYWwuICgyMDIxKV0oaHR0cHM6Ly93d3cudGFuZGZvbmxpbmUuY29tL2VwcmludC9ZS0dTOEdUS0RCS1lGWEVHRldTQi9mdWxsP3RhcmdldD0xMC4xMDgwLzAwOTQ5NjU1LjIwMjEuMTg5MTUzOSkNCi0gW0Zpc2NoZXIgYW5kIE1vdWdlb3QgKDIwMTkpXShodHRwczovL3d3dy5zY2llbmNlZGlyZWN0LmNvbS9zY2llbmNlL2FydGljbGUvcGlpL1MwMzc4Mzc1ODE4MzAyMzQ5KQ0KLSBbSGFzICgyMDIxKV0oaHR0cHM6Ly9oYWwuYXJjaGl2ZXMtb3V2ZXJ0ZXMuZnIvaGFsLTAyODg0MzMzdjUpDQotIFtCaWF1IGV0IGFsLiAoMjAxNildKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzAwNDcyNTlYMTUwMDA5NTApDQotIC4uLg0KLSBbZHBseXIgdmlkZW9zXShodHRwczovL3d3dy55b3V0dWJlLmNvbS9oYXNodGFnL2RwbHlyKSBgciBmb250YXdlc29tZTo6ZmEoInZpZGVvIilgDQotIFtnZ3Bsb3QyIHZpZGVvIHR1dG9yaWFsXShodHRwczovL3d3dy55b3V0dWJlLmNvbS9oYXNodGFnL2dncGxvdDIpIGByIGZvbnRhd2Vzb21lOjpmYSgidmlkZW8iKWANCi0gW1IgZm9yIGRhdGEgc2NpZW5jZV0oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei8pDQoNCi0tLQ==